structx 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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