jsof 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c83f6ee1b4cb426245690720ddfe8d25cc64a80bdb432a37e4dea766a9d066c8
4
+ data.tar.gz: e55a4b4a98f039ff9c864318a72649be3ff363add77e46d693b3e648da1ee85a
5
+ SHA512:
6
+ metadata.gz: 4285733477cc69de5f682d26c21f3069946341eece8eb826ea67fc69985bcba42cd87f4c0b0c53a1e476bbe6c9d437c07f58bf310f41a1499225841209f88a6e
7
+ data.tar.gz: 874d2e9d7ecf1dd735f0398a679477f4aae5b4530b9624e49adbbb8d9b7dfbfc80d0665b5040cf356c21c546f8b53919288dde528a8c3de6fc4047385ce5a7be
data/.rubocop.yml ADDED
@@ -0,0 +1,13 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in jsof.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "rubocop", "~> 1.21"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 Riki Ishikawa
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # Jsof
2
+
3
+ Jsof is library to utilize manipuration of JSON object in Ruby.
4
+ It provides wrapper class for Hash and Array to access properties like JavaScript fasion.
5
+ If you want, you can define properties explicitly, and add type to them.
6
+
7
+ (Jsof stand for "JavaScript of".)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'jsof'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle install
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install jsof
24
+
25
+ ## Usage
26
+
27
+ ### For normal use
28
+
29
+ ```ruby
30
+ require 'jsof'
31
+
32
+ x = Jsof({a: 1, b: {c: 'a'}})
33
+ p x.a # => 1
34
+ p x.b.c # => 'a'
35
+ x.d = 3
36
+ x.e = {ee: 4}
37
+ p x.to_h # => {a: 1, b: {c: 'a'}, d: 3, e: {ee: 4}}
38
+ ```
39
+
40
+ ### define explicit attributes
41
+
42
+ ```ruby
43
+ require 'jsof'
44
+
45
+ class X < Jsof::WrapObject
46
+ define_attr :a # untyped property
47
+ define_attr :b, type: Integer # typed property
48
+ define_attr :c, type: Array # untyped array property
49
+ end
50
+
51
+ x = X.new
52
+ x.a = 1
53
+ x.b = 'a' # raise TypeError
54
+ x.c = [1, 2]
55
+ x.d = 1 # raise NoMethodError
56
+ ```
57
+
58
+ Also, you can define nested structure with type.
59
+
60
+ ```ruby
61
+ class Y < Jsof::WrapObject
62
+ define_attr :xx, type: X
63
+ end
64
+
65
+ y = Y.new
66
+ y.x = {}
67
+ y.x.a = 1
68
+ y.x.unknown = 1 # raise TypeError
69
+
70
+ y.x = {unknown: 1} # Currently, this kind type check is not implemented.
71
+ ```
72
+
73
+
74
+ ## Development
75
+
76
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
77
+
78
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
79
+
80
+ ## Contributing
81
+
82
+ Bug reports and pull requests are welcome on GitHub at https://github.com/jljse/jsof.
83
+
84
+ ## License
85
+
86
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "jsof"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/jsof.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/jsof/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "jsof"
7
+ spec.version = Jsof::VERSION
8
+ spec.authors = ["Riki Ishikawa"]
9
+ spec.email = ["riki.ishikawa@gmail.com"]
10
+
11
+ spec.summary = "JSON object wrapper to access properties like JavaScript."
12
+ # spec.description = "TODO: Write a longer description or delete this line."
13
+ spec.homepage = "https://github.com/jljse/jsof/"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/jljse/jsof/"
21
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ # spec.add_dependency "example-gem", "~> 1.0"
36
+
37
+ # For more information and examples about making a new gem, check out our
38
+ # guide at: https://bundler.io/guides/creating_gem.html
39
+ end
@@ -0,0 +1,13 @@
1
+
2
+ # Wrap JSON like object
3
+ # @pram [Hash] obj Wrap target object
4
+ # @return [Jsof::WrapObject, Jsof::WrapArray]
5
+ def Jsof(obj = {})
6
+ if obj.is_a? Hash
7
+ return Jsof::WrapObject.new(obj)
8
+ end
9
+ if obj.is_a? Array
10
+ return Jsof::WrapArray.new(obj)
11
+ end
12
+ obj
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsof
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,84 @@
1
+
2
+ # Wrapper class for Array.
3
+ class Jsof::WrapArray
4
+ # Create wrapper for Array
5
+ # @param [Array] obj Wrap target array
6
+ # @param [Class, Jsof::WrapArrayType] typeof_element Type of each element
7
+ def initialize(obj = [], typeof_element: nil)
8
+ @internal_object = obj
9
+ @wrapped = []
10
+ @typeof_element = typeof_element
11
+ end
12
+
13
+ # Return reference to wrapped array.
14
+ def internal_object
15
+ @internal_object
16
+ end
17
+
18
+ def typeof_element
19
+ @typeof_element
20
+ end
21
+
22
+ def [](index)
23
+ raise ArgumentError unless index.is_a? Integer
24
+
25
+ return @wrapped[index] if @wrapped[index]
26
+
27
+ elem = @internal_object[index]
28
+ boxed = Jsof::WrapHelper.boxing(elem, typeof_element)
29
+ @wrapped[index] = boxed if elem != boxed
30
+ return boxed
31
+ end
32
+
33
+ def []=(index, val)
34
+ raise ArgumentError unless index.is_a? Integer
35
+
36
+ if typeof_element
37
+ raise TypeError unless Jsof::WrapHelper.assignable?(typeof_element, val)
38
+ end
39
+
40
+ boxed = Jsof::WrapHelper.boxing(val, typeof_element)
41
+ @wrapped[index] = boxed if Jsof::WrapHelper.wrapped_value?(boxed)
42
+ @internal_object[index] = Jsof::WrapHelper.unboxing(val)
43
+ val
44
+ end
45
+
46
+ # Clean internal cache.
47
+ # Call this after you changed wrapped object directly.
48
+ def clear_internal
49
+ @wrapped.clear
50
+ end
51
+
52
+ # @return [Integer]
53
+ def size
54
+ @internal_object.size
55
+ end
56
+
57
+ # Delete element. (Same as Array#delete_at)
58
+ # @param [Integer] pos Element index to delete
59
+ def delete_at(pos)
60
+ @internal_object.delete_at(pos)
61
+ @wrapped.delete_at(pos)
62
+ end
63
+
64
+ include Enumerable
65
+ def each
66
+ if block_given?
67
+ for i in 0...@internal_object.size
68
+ yield self[i]
69
+ end
70
+ else
71
+ Enumerator.new(self, :each)
72
+ end
73
+ end
74
+
75
+ # Return reference to wrapped array.
76
+ def to_ary
77
+ @internal_object
78
+ end
79
+
80
+ # Return reference to wrapped array.
81
+ def to_a
82
+ @internal_object
83
+ end
84
+ end
@@ -0,0 +1,24 @@
1
+
2
+ # Used with define_attr to define typed array property.
3
+ class Jsof::WrapArrayType
4
+ # @param [Class, WrapArrayType] typeof_element Type of each element
5
+ def initialize(typeof_element: nil)
6
+ @typeof_element = typeof_element
7
+ end
8
+
9
+ # Create wrapper instance.
10
+ # @return [Jsof::WrapArray]
11
+ def new(obj)
12
+ Jsof::WrapArray.new(obj, typeof_element: @typeof_element)
13
+ end
14
+
15
+ # Check value can be wrapped by this type.
16
+ # @return [Boolean]
17
+ def assignable?(val)
18
+ if @typeof_element && val
19
+ return false unless val.is_a? Array
20
+ return val.all?{|elem| Jsof::WrapHelper.assignable?(@typeof_element, elem)}
21
+ end
22
+ true
23
+ end
24
+ end
@@ -0,0 +1,69 @@
1
+
2
+
3
+ # Helper methods.
4
+ module Jsof::WrapHelper
5
+ # @return [Boolean] Already wrapped or not
6
+ def self.wrapped_value?(val)
7
+ return true if val.is_a?(Jsof::WrapObject)
8
+ return true if val.is_a?(Jsof::WrapArray)
9
+ return false
10
+ end
11
+
12
+ # @return [Boolean] Wrappable by Jsof::WrapObject or not
13
+ def self.require_wrap_value?(val)
14
+ return true if val.is_a?(Hash)
15
+ return false
16
+ end
17
+
18
+ # @return [Boolean] Wrappable by Jsof::WrapArray or not
19
+ def self.require_array_wrap_value?(val)
20
+ return true if val.is_a?(Array)
21
+ return false
22
+ end
23
+
24
+ # Wrap value if wrappable.
25
+ # @param [Object] val Wrap target object
26
+ # @param [Class, Jsof::WrapArrayType] Wrapper type. if nil, use WrapObject/WrapArray.
27
+ def self.boxing(val, wrapper_type)
28
+ if require_wrap_value?(val)
29
+ return wrapper_type.new(val) if wrapper_type
30
+ return Jsof::WrapObject.new(val)
31
+ end
32
+ if require_array_wrap_value?(val)
33
+ return wrapper_type.new(val) if wrapper_type
34
+ return Jsof::WrapArray.new(val)
35
+ end
36
+ return val
37
+ end
38
+
39
+ # Take internal object if wrapped.
40
+ # @param [Object] val Unwrap target object
41
+ def self.unboxing(val)
42
+ if wrapped_value?(val)
43
+ return val.internal_object
44
+ end
45
+ return val
46
+ end
47
+
48
+ # Check value can be wrapped by specific type.
49
+ # @param [Class, Jsof::WrapArrayType] type Expected type
50
+ # @param [Object] val Check target object
51
+ # @return [Boolean]
52
+ def self.assignable?(type, val)
53
+ return true unless val
54
+
55
+ if type.is_a? Jsof::WrapArrayType
56
+ return type.assignable?(val)
57
+ end
58
+ if type == Jsof::BooleanType
59
+ return val == true || val == false
60
+ end
61
+ if type < Jsof::WrapObject
62
+ return type.assignable?(val)
63
+ end
64
+ if type.is_a? Class
65
+ return val.is_a? type
66
+ end
67
+ true
68
+ end
69
+ end
@@ -0,0 +1,134 @@
1
+ # Wrapper class for Object(Hash).
2
+ class Jsof::WrapObject
3
+ # Create wrapper for Hash.
4
+ # @param [Hash] obj Wrap target hash
5
+ # @param [Boolean] allow_implicit_attr
6
+ # Allow to use undefined properties or not.
7
+ # If nil, assume true if it's instance of WrapObject,
8
+ # and assume false if it's instance of derived classes.
9
+ def initialize(obj = {}, allow_implicit_attr: nil)
10
+ unless self.class.initialized?
11
+ self.class.resolve_typeof_properties
12
+ self.class.initialize()
13
+ self.class.initialized = true
14
+ end
15
+
16
+ @internal_object = obj
17
+ @wrapped = {}
18
+ @is_allow_implicit_attr = allow_implicit_attr
19
+ if @is_allow_implicit_attr == nil
20
+ @is_allow_implicit_attr = self.class.typeof_properties.empty?
21
+ end
22
+ end
23
+
24
+ @typeof_properties = {}
25
+ def self.inherited(subclass)
26
+ subclass.instance_variable_set(:@typeof_properties, @typeof_properties.dup)
27
+ end
28
+ # Explicitly defined properties.
29
+ # @return [Hash{Symbol => Class, Jsof::WrapArrayType, Proc}]
30
+ def self.typeof_properties
31
+ @typeof_properties
32
+ end
33
+ def self.resolve_typeof_properties
34
+ @typeof_properties.keys.each do |sym|
35
+ if @typeof_properties[sym].is_a? Proc
36
+ @typeof_properties[sym] = @typeof_properties[sym].call
37
+ end
38
+ end
39
+ end
40
+ @initialized = false
41
+ # Whether self.initialize was already called or not.
42
+ def self.initialized?
43
+ @initialized
44
+ end
45
+ def self.initialized=(val)
46
+ @initialized = val
47
+ end
48
+ # Place to define properties by define_attr.
49
+ # Override this for derived class.
50
+ # This method will be called when the first instace is created.
51
+ def self.initialize
52
+ # override
53
+ end
54
+
55
+ # Define explicit accessor methods for attribute(property).
56
+ # @param [Symbol] sym Attribute name.
57
+ # @param [Class, Jsof::WrapArrayType, Proc] type
58
+ # If nil, the property is untyped.
59
+ # Usually, pass Class to specify the type of the property.
60
+ # If you want to specify typed Array, then pass instance of Jsof::WrapArrayType.
61
+ # If you want to use the class not defined yet (defined later) , then wrap with Proc like `->{ YourClass }`.
62
+ def self.define_attr(sym, type: nil)
63
+ @typeof_properties[sym] = type
64
+ self.define_method(sym) do
65
+ self[sym]
66
+ end
67
+ self.define_method((sym.to_s + "=").to_sym) do |val|
68
+ self[sym] = val
69
+ end
70
+ end
71
+
72
+ # Check value can be wrapped by this type.
73
+ # TODO: Currently, will not check properties's types,
74
+ # but this behavior might be changed in future version.
75
+ # @return [Boolean]
76
+ def self.assignable?(val)
77
+ # TODO:
78
+ return true if val == nil
79
+ return true if val.is_a? Hash
80
+ false
81
+ end
82
+
83
+ # Return reference to wrapped array.
84
+ def internal_object
85
+ @internal_object
86
+ end
87
+
88
+ def [](key)
89
+ sym = key.to_sym
90
+ return @wrapped[sym] if @wrapped.key?(sym)
91
+
92
+ elem = @internal_object[sym]
93
+ boxed = Jsof::WrapHelper.boxing(elem, self.class.typeof_properties[sym])
94
+ @wrapped[sym] = boxed if elem != boxed
95
+ return boxed
96
+ end
97
+
98
+ def []=(key, val)
99
+ sym = key.to_sym
100
+ if type = self.class.typeof_properties[sym]
101
+ raise TypeError unless Jsof::WrapHelper.assignable?(type, val)
102
+ end
103
+ boxed = Jsof::WrapHelper.boxing(val, self.class.typeof_properties[sym])
104
+ @wrapped[sym] = boxed if Jsof::WrapHelper.wrapped_value?(boxed)
105
+ @internal_object[sym] = Jsof::WrapHelper.unboxing(val)
106
+ val
107
+ end
108
+
109
+ # Clean internal cache.
110
+ # Call this after you changed wrapped object directly.
111
+ def clear_internal
112
+ @wrapped.clear
113
+ end
114
+
115
+ def method_missing(name, *args)
116
+ raise NoMethodError.new("", name) unless @is_allow_implicit_attr
117
+
118
+ if name.to_s.end_with?('=')
119
+ self[name.to_s[0...-1].to_sym] = args.first
120
+ else
121
+ self[name]
122
+ end
123
+ end
124
+
125
+ # Return reference to wrapped array.
126
+ def to_hash
127
+ @internal_object
128
+ end
129
+
130
+ # Return reference to wrapped array.
131
+ def to_h
132
+ @internal_object
133
+ end
134
+ end
data/lib/jsof.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jsof/version"
4
+
5
+ module Jsof
6
+ class BooleanType end
7
+ end
8
+
9
+ require_relative 'jsof/wrap_array'
10
+ require_relative 'jsof/wrap_array_type'
11
+ require_relative 'jsof/wrap_object'
12
+ require_relative 'jsof/wrap_helper'
13
+ require_relative 'jsof/global'
data/sig/jsof.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Jsof
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,61 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsof
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Riki Ishikawa
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-01-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description:
14
+ email:
15
+ - riki.ishikawa@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rubocop.yml"
21
+ - Gemfile
22
+ - LICENSE.txt
23
+ - README.md
24
+ - Rakefile
25
+ - bin/console
26
+ - bin/setup
27
+ - jsof.gemspec
28
+ - lib/jsof.rb
29
+ - lib/jsof/global.rb
30
+ - lib/jsof/version.rb
31
+ - lib/jsof/wrap_array.rb
32
+ - lib/jsof/wrap_array_type.rb
33
+ - lib/jsof/wrap_helper.rb
34
+ - lib/jsof/wrap_object.rb
35
+ - sig/jsof.rbs
36
+ homepage: https://github.com/jljse/jsof/
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ homepage_uri: https://github.com/jljse/jsof/
41
+ source_code_uri: https://github.com/jljse/jsof/
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: 2.7.0
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ requirements: []
57
+ rubygems_version: 3.3.3
58
+ signing_key:
59
+ specification_version: 4
60
+ summary: JSON object wrapper to access properties like JavaScript.
61
+ test_files: []