structx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ vendor
19
+ html
data/.simplecov ADDED
@@ -0,0 +1,8 @@
1
+ require 'coveralls'
2
+ Coveralls.wear!
3
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
4
+ SimpleCov::Formatter::HTMLFormatter,
5
+ Coveralls::SimpleCov::Formatter
6
+ ]
7
+ SimpleCov.command_name 'bacon'
8
+ SimpleCov.start {add_filter 'test'}
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.0.0"
6
+ - jruby-19mode # JRuby in 1.9 mode
7
+ - rbx-19mode
8
+ script: rake test
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in forwardablex.gemspec
4
+ gemspec
5
+
6
+ gem 'simplecov', :require => false, :group => :test
7
+ gem 'coveralls', :require => false, :group => :test
8
+ gem 'json', '~> 1.7.7'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Keita Yamaguchi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # StructX
2
+
3
+ StructX is an extension of Ruby standard Struct. The diffences are that 1) the constructor handles hash table as key-value pairs, 2) you can specify members as statements, and 3) you can set default values of member. StructX's API is compatible with Struct.
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/structx.png)](http://badge.fury.io/rb/structx) [![Build Status](https://travis-ci.org/keita/structx.png?branch=master)](https://travis-ci.org/keita/structx) [![Coverage Status](https://coveralls.io/repos/keita/structx/badge.png?branch=master)](https://coveralls.io/r/keita/structx) [![Code Climate](https://codeclimate.com/github/keita/structx.png)](https://codeclimate.com/github/keita/structx)
6
+
7
+ ## Installation
8
+
9
+ $ gem install structx
10
+
11
+ ## Usage
12
+
13
+ ### Constructor with hash table
14
+
15
+ ```ruby
16
+ StructX.new(:x, :y, :z).new(x: 1, y: 2, z: 3) #=> #<struct x=1, y=10, z=100>
17
+ ```
18
+
19
+ ### Member sentences
20
+
21
+ ```ruby
22
+ class A < StructX
23
+ member :x
24
+ member :y
25
+ member :z
26
+ end
27
+ A.new(1, 2, 3) #=> #<struct A x=1, y=2, z=3>
28
+ ```
29
+
30
+ ### Default values
31
+
32
+ ```ruby
33
+ class B < StructX
34
+ member :x
35
+ member :y, default: 10
36
+ member :z, default: 100
37
+ end
38
+ B.new(1) # => #<struct B x=1, y=10, z=100>
39
+ ```
40
+
41
+ ## Documentation
42
+
43
+ - [API Documentation](http://rubydoc.info/gems/structx)
44
+
45
+ ## License
46
+
47
+ StructX is free software distributed under MIT license.
48
+ The following files are copied from ruby's test case, so you should keep its lisense.
49
+
50
+ - test/ruby/1.9/test_struct.rb
51
+ - test/ruby/1.9/envutil.rb
52
+ - test/ruby/2.0/test_struct.rb
53
+ - test/ruby/2.0/envutil.rb
54
+
55
+ ## Contributing
56
+
57
+ 1. Fork it
58
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
59
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
60
+ 4. Push to the branch (`git push origin my-new-feature`)
61
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc 'Test specs'
4
+ task 'test' do
5
+ sh "test/run.sh"
6
+ end
7
+
8
+ desc 'Generate API document'
9
+ task 'html' do
10
+ sh "bundle exec yard doc -o html --hide-void-return --no-api"
11
+ end
12
+
13
+ desc 'Show undocumented function list'
14
+ task 'html:undoc' do
15
+ sh "bundle exec yard stats --list-undoc --no-api --compact"
16
+ end
17
+
18
+ task :default => :test
data/lib/structx.rb ADDED
@@ -0,0 +1,169 @@
1
+ require "forwardablex"
2
+ require "structx/version"
3
+
4
+ # StructX is an extension of Ruby standard Struct. The diffences are that 1) the
5
+ # constructor handles hash table as key-value pairs, 2) you can specify members
6
+ # as statements, and 3) you can set default values of member. StructX's API is
7
+ # compatible with Struct.
8
+ #
9
+ # @example Constructor with hash table
10
+ # StructX.new(:x, :y, :z).new(x: 1, y: 2, z: 3) #=> #<struct x=1, y=10, z=100>
11
+ # @example Member sentences
12
+ # class A < StructX
13
+ # member :x
14
+ # member :y
15
+ # member :z
16
+ # end
17
+ # A.new(1, 2, 3) #=> #<struct A x=1, y=2, z=3>
18
+ # @example Default values
19
+ # class B < StructX
20
+ # member :x
21
+ # member :y, default: 10
22
+ # member :z, default: 100
23
+ # end
24
+ # B.new(1) # => #<struct B x=1, y=10, z=100>
25
+ class StructX
26
+ include Enumerable
27
+
28
+ class << self
29
+ alias :orig_new :new
30
+ private :orig_new
31
+
32
+ # Same as Struct[].
33
+ alias :[] :new
34
+
35
+ # Create a instance or sublcass. If this class has members, create an
36
+ # instance. The case handles hash table as key-value pairs. If this class
37
+ # has no members, create a subclass.
38
+ #
39
+ # @param args [Array or Hash]
40
+ # Same as Struct if args is Array. Consider args as key-value pairs if it is Hash.
41
+ def new(*args)
42
+ # create an instance
43
+ return orig_new(*args) if @member
44
+
45
+ # create subclass
46
+ Class.new(StructX).tap do |subclass|
47
+ # class name
48
+ if args.first.kind_of?(String)
49
+ const_set(args.first, subclass)
50
+ args = args.drop(1)
51
+ end
52
+
53
+ # set members
54
+ args.each {|m| subclass.member(*m)}
55
+
56
+ # this is according to MRI, why yield?
57
+ yield if block_given?
58
+ end
59
+ end
60
+
61
+ # Same as Struct#members.
62
+ def members
63
+ (@member ||= {}).keys
64
+ end
65
+
66
+ # Add member into structure.
67
+ #
68
+ # @param name [Symbol]
69
+ # member name
70
+ # @param data [Hash]
71
+ # member options
72
+ def member(name, data={})
73
+ (@member ||= {})[name] = Hash.new.merge(data)
74
+
75
+ # define member's value reader
76
+ define_method(name) do
77
+ @value[name]
78
+ end
79
+
80
+ # define member's value writer
81
+ define_method("%s=" % name) do |val|
82
+ @value[name] = val
83
+ end
84
+ end
85
+
86
+ # Return default values.
87
+ #
88
+ # @return [Hash{Symbol=>Object}]
89
+ # default values
90
+ def default_values
91
+ @member.inject({}) do |tbl, (key, val)|
92
+ tbl.tap {|x| x[key] = val[:default] if val.has_key?(:default)}
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def inherited(subclass)
99
+ @member.each {|key, data| subclass.member(key, data)} if @member
100
+ end
101
+ end
102
+
103
+ # almost methods are forwarded to value table
104
+ forward :class, :members
105
+ forward :@value, :each, :each_pair
106
+ forward! :@value, :values, :length, :size, :hash
107
+ forward! lambda{|x| @value.values}, :each, :values_at
108
+
109
+ alias :to_a :values
110
+
111
+ # See Struct.new.
112
+ def initialize(*values)
113
+ if values.first.kind_of?(Hash) and values.size == 1
114
+ @value = __build__(values.first)
115
+ else
116
+ raise ArgumentError.new("struct size differs #{values} #{members} ") if values.size > members.size
117
+ @value = __build__(members.zip(values))
118
+ end
119
+ end
120
+
121
+ # Same as Struct#[].
122
+ def [](idx)
123
+ case idx
124
+ when Integer
125
+ size > idx && -size <= idx ? values[idx] : (raise IndexError.new(idx))
126
+ when Symbol, String
127
+ members.include?(idx.to_sym) ? @value[idx.to_sym] : (raise NameError.new(idx.to_s))
128
+ end
129
+ end
130
+
131
+ # Same as Struct#[]=.
132
+ def []=(idx, val)
133
+ case idx
134
+ when Integer
135
+ size > idx && -size <= idx ? @value[members[idx]] = val : (raise IndexError.new(idx))
136
+ when Symbol, String
137
+ members.include?(idx.to_sym) ? @value[idx.to_sym] = val : (raise NameError.new(idx.to_s))
138
+ end
139
+ end
140
+
141
+ # Same as Struct#inspect.
142
+ def inspect
143
+ name = self.class.inspect[0] == "#" ? "" : " " + self.class.inspect
144
+ values = @value.map do |key, val|
145
+ k = (key.to_s[0] == "@" ? ":" : "") + key.to_s
146
+ v = self == val ? "#<struct %s:...>" % val : val.inspect
147
+ "%s=%s" % [k, v]
148
+ end
149
+ "#<struct%s %s>" % [name, values.join(", ")]
150
+ end
151
+
152
+ # Same as Struct#eql?.
153
+ def eql?(other)
154
+ self.class == other.class and @value == other.to_h
155
+ end
156
+ alias :"==" :eql?
157
+
158
+ # Same as Struct#to_h. This method is available in Ruby 1.9 too.
159
+ def to_h
160
+ @value
161
+ end
162
+
163
+ private
164
+
165
+ def __build__(data)
166
+ tbl = data.inject({}) {|tbl, (m, val)| tbl.tap {|x| x[m] = val if val}}
167
+ self.class.default_values.merge(tbl)
168
+ end
169
+ end
@@ -0,0 +1,4 @@
1
+ class StructX
2
+ # version of StructX
3
+ VERSION = "0.1.0"
4
+ end
data/structx.gemspec ADDED
@@ -0,0 +1,29 @@
1
+ # -*- ruby -*-
2
+ # coding: utf-8
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'structx/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "structx"
9
+ spec.version = StructX::VERSION
10
+ spec.authors = ["Keita Yamaguchi"]
11
+ spec.email = ["keita.yamaguchi@gmail.com"]
12
+ spec.description = "sturctx is a Ruby library that extends standard Struct"
13
+ spec.summary = "sturctx is a Ruby library that extends standard Struct"
14
+ spec.homepage = "https://github.com/keita/structx"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "forwardablex"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "bacon"
27
+ spec.add_development_dependency "yard"
28
+ spec.add_development_dependency "ruby-version"
29
+ end
@@ -0,0 +1,32 @@
1
+ require "structx"
2
+ require "ruby-version"
3
+
4
+ def patch(code)
5
+ ## Struct -> StructX
6
+ code = code.gsub("Struct", "StructX")
7
+ ## clear require_relative
8
+ code = code.gsub(/^require_relative.*$/, "")
9
+ ## modify error message for test_struct_subclass
10
+ # StructX can handle security error but the message is different
11
+ code = code.gsub("Insecure: can't modify \#\{st}::S", "Insecure: can't modify hash")
12
+ end
13
+
14
+ def load_test(target)
15
+ require_relative "../test/ruby/#{target}/envutil"
16
+ path = File.join(File.dirname(__FILE__), "ruby", target, "test_struct.rb")
17
+ eval patch(File.read(path))
18
+ end
19
+
20
+ if Ruby::Engine::NAME == "ruby"
21
+ if Ruby::Version >= "1.9" and Ruby::Version < "2.0"
22
+ load_test("1.9")
23
+ end
24
+
25
+ if Ruby::Version >= "2.0"
26
+ load_test("2.0")
27
+ end
28
+ else
29
+ puts "We cannot run compatibility test with jruby and rbx."
30
+ puts "Related to Struct, these VMs are not compatible with MRI."
31
+ end
32
+
@@ -0,0 +1,212 @@
1
+ require "open3"
2
+ require "timeout"
3
+
4
+ module EnvUtil
5
+ def rubybin
6
+ unless ENV["RUBYOPT"]
7
+
8
+ end
9
+ if ruby = ENV["RUBY"]
10
+ return ruby
11
+ end
12
+ ruby = "ruby"
13
+ rubyexe = ruby+".exe"
14
+ 3.times do
15
+ if File.exist? ruby and File.executable? ruby and !File.directory? ruby
16
+ return File.expand_path(ruby)
17
+ end
18
+ if File.exist? rubyexe and File.executable? rubyexe
19
+ return File.expand_path(rubyexe)
20
+ end
21
+ ruby = File.join("..", ruby)
22
+ end
23
+ if defined?(RbConfig.ruby)
24
+ RbConfig.ruby
25
+ else
26
+ "ruby"
27
+ end
28
+ end
29
+ module_function :rubybin
30
+
31
+ LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
32
+
33
+ def invoke_ruby(args, stdin_data="", capture_stdout=false, capture_stderr=false, opt={})
34
+ in_c, in_p = IO.pipe
35
+ out_p, out_c = IO.pipe if capture_stdout
36
+ err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
37
+ opt = opt.dup
38
+ opt[:in] = in_c
39
+ opt[:out] = out_c if capture_stdout
40
+ opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
41
+ if enc = opt.delete(:encoding)
42
+ out_p.set_encoding(enc) if out_p
43
+ err_p.set_encoding(enc) if err_p
44
+ end
45
+ timeout = opt.delete(:timeout) || 10
46
+ c = "C"
47
+ child_env = {}
48
+ LANG_ENVS.each {|lc| child_env[lc] = c}
49
+ if Array === args and Hash === args.first
50
+ child_env.update(args.shift)
51
+ end
52
+ args = [args] if args.kind_of?(String)
53
+ pid = spawn(child_env, EnvUtil.rubybin, *args, opt)
54
+ in_c.close
55
+ out_c.close if capture_stdout
56
+ err_c.close if capture_stderr && capture_stderr != :merge_to_stdout
57
+ if block_given?
58
+ return yield in_p, out_p, err_p, pid
59
+ else
60
+ th_stdout = Thread.new { out_p.read } if capture_stdout
61
+ th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
62
+ in_p.write stdin_data.to_str
63
+ in_p.close
64
+ if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
65
+ stdout = th_stdout.value if capture_stdout
66
+ stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
67
+ else
68
+ raise Timeout::Error
69
+ end
70
+ out_p.close if capture_stdout
71
+ err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
72
+ Process.wait pid
73
+ status = $?
74
+ return stdout, stderr, status
75
+ end
76
+ ensure
77
+ [in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
78
+ io.close if io && !io.closed?
79
+ end
80
+ [th_stdout, th_stderr].each do |th|
81
+ (th.kill; th.join) if th
82
+ end
83
+ end
84
+ module_function :invoke_ruby
85
+
86
+ alias rubyexec invoke_ruby
87
+ class << self
88
+ alias rubyexec invoke_ruby
89
+ end
90
+
91
+ def verbose_warning
92
+ class << (stderr = "")
93
+ alias write <<
94
+ end
95
+ stderr, $stderr, verbose, $VERBOSE = $stderr, stderr, $VERBOSE, true
96
+ yield stderr
97
+ ensure
98
+ stderr, $stderr, $VERBOSE = $stderr, stderr, verbose
99
+ return stderr
100
+ end
101
+ module_function :verbose_warning
102
+
103
+ def suppress_warning
104
+ verbose, $VERBOSE = $VERBOSE, nil
105
+ yield
106
+ ensure
107
+ $VERBOSE = verbose
108
+ end
109
+ module_function :suppress_warning
110
+
111
+ def under_gc_stress
112
+ stress, GC.stress = GC.stress, true
113
+ yield
114
+ ensure
115
+ GC.stress = stress
116
+ end
117
+ module_function :under_gc_stress
118
+ end
119
+
120
+ module Test
121
+ module Unit
122
+ module Assertions
123
+ public
124
+ def assert_normal_exit(testsrc, message = '', opt = {})
125
+ if opt.include?(:child_env)
126
+ opt = opt.dup
127
+ child_env = [opt.delete(:child_env)] || []
128
+ else
129
+ child_env = []
130
+ end
131
+ out, _, status = EnvUtil.invoke_ruby(child_env + %W'-W0', testsrc, true, :merge_to_stdout, opt)
132
+ pid = status.pid
133
+ faildesc = proc do
134
+ signo = status.termsig
135
+ signame = Signal.list.invert[signo]
136
+ sigdesc = "signal #{signo}"
137
+ if signame
138
+ sigdesc = "SIG#{signame} (#{sigdesc})"
139
+ end
140
+ if status.coredump?
141
+ sigdesc << " (core dumped)"
142
+ end
143
+ full_message = ''
144
+ if !message.empty?
145
+ full_message << message << "\n"
146
+ end
147
+ full_message << "pid #{pid} killed by #{sigdesc}"
148
+ if !out.empty?
149
+ out << "\n" if /\n\z/ !~ out
150
+ full_message << "\n#{out.gsub(/^/, '| ')}"
151
+ end
152
+ full_message
153
+ end
154
+ assert !status.signaled?, faildesc
155
+ end
156
+
157
+ def assert_in_out_err(args, test_stdin = "", test_stdout = [], test_stderr = [], message = nil, opt={})
158
+ stdout, stderr, status = EnvUtil.invoke_ruby(args, test_stdin, true, true, opt)
159
+ if block_given?
160
+ yield(stdout.lines.map {|l| l.chomp }, stderr.lines.map {|l| l.chomp })
161
+ else
162
+ if test_stdout.is_a?(Regexp)
163
+ assert_match(test_stdout, stdout, message)
164
+ else
165
+ assert_equal(test_stdout, stdout.lines.map {|l| l.chomp }, message)
166
+ end
167
+ if test_stderr.is_a?(Regexp)
168
+ assert_match(test_stderr, stderr, message)
169
+ else
170
+ assert_equal(test_stderr, stderr.lines.map {|l| l.chomp }, message)
171
+ end
172
+ status
173
+ end
174
+ end
175
+
176
+ def assert_ruby_status(args, test_stdin="", message=nil, opt={})
177
+ _, _, status = EnvUtil.invoke_ruby(args, test_stdin, false, false, opt)
178
+ m = message ? "#{message} (#{status.inspect})" : "ruby exit status is not success: #{status.inspect}"
179
+ assert(status.success?, m)
180
+ end
181
+
182
+ def assert_warn(msg)
183
+ stderr = EnvUtil.verbose_warning { yield }
184
+ assert(msg === stderr, "warning message #{stderr.inspect} is expected to match #{msg.inspect}")
185
+ end
186
+
187
+
188
+ def assert_is_minus_zero(f)
189
+ assert(1.0/f == -Float::INFINITY, "#{f} is not -0.0")
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ begin
196
+ require 'rbconfig'
197
+ rescue LoadError
198
+ else
199
+ module RbConfig
200
+ @ruby = EnvUtil.rubybin
201
+ class << self
202
+ undef ruby if method_defined?(:ruby)
203
+ attr_reader :ruby
204
+ end
205
+ dir = File.dirname(ruby)
206
+ name = File.basename(ruby, CONFIG['EXEEXT'])
207
+ CONFIG['bindir'] = dir
208
+ CONFIG['ruby_install_name'] = name
209
+ CONFIG['RUBY_INSTALL_NAME'] = name
210
+ Gem::ConfigMap[:bindir] = dir if defined?(Gem::ConfigMap)
211
+ end
212
+ end