kt_data_class 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 +7 -0
- data/.circleci/config.yml +32 -0
- data/.gitignore +12 -0
- data/Gemfile +6 -0
- data/README.md +96 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/kt_data_class.gemspec +28 -0
- data/lib/kt_data_class.rb +12 -0
- data/lib/kt_data_class/base.rb +57 -0
- data/lib/kt_data_class/definition.rb +29 -0
- data/lib/kt_data_class/factory.rb +24 -0
- data/lib/kt_data_class/version.rb +3 -0
- metadata +112 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 07f4f37597afbff2e75aa9132f44f00e1496d7451228ac61622116a90723109e
|
4
|
+
data.tar.gz: 0b3ac170ffdbbde406f76b1354bf5e7471486698f4e54e413583d48017cea832
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 304dbdb54529139130390ee98d20468e92f10f0a391a401fa2e6d2a7fe6287f67247337ae0a3fa672d2b4fdb031f1715a43c144f984b24368397167258560e88
|
7
|
+
data.tar.gz: 5ca39bda402610f31a4e0bc4bb0542d88da2be15cd788f8197bcc32062f29804bf68097b55acf1957088155f7b4b07adde49bfe6dfd46e3a2808d40e5d9c91c6
|
@@ -0,0 +1,32 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.3.8
|
6
|
+
|
7
|
+
working_directory: ~/repo
|
8
|
+
|
9
|
+
steps:
|
10
|
+
- checkout
|
11
|
+
- run:
|
12
|
+
name: install dependencies
|
13
|
+
command: |
|
14
|
+
bundle install --jobs=4 --path vendor/bundle
|
15
|
+
|
16
|
+
- run:
|
17
|
+
name: run tests
|
18
|
+
command: |
|
19
|
+
mkdir /tmp/test-results
|
20
|
+
TEST_FILES="$(circleci tests glob "spec/*_spec.rb" | circleci tests split --split-by=timings)"
|
21
|
+
|
22
|
+
bundle exec rspec --format progress \
|
23
|
+
--format RspecJunitFormatter \
|
24
|
+
--out /tmp/test-results/rspec.xml \
|
25
|
+
--format progress \
|
26
|
+
$TEST_FILES
|
27
|
+
|
28
|
+
- store_test_results:
|
29
|
+
path: /tmp/test-results
|
30
|
+
- store_artifacts:
|
31
|
+
path: /tmp/test-results
|
32
|
+
destination: test-results
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# KtDataClass
|
2
|
+
|
3
|
+
Kotlinの
|
4
|
+
|
5
|
+
```
|
6
|
+
data class Point(val x: Int, val y: Int)
|
7
|
+
|
8
|
+
val p1 = Point(3, 4)
|
9
|
+
```
|
10
|
+
|
11
|
+
のように書けるやつをRubyで作ってみようという試み。
|
12
|
+
|
13
|
+
## Feature
|
14
|
+
|
15
|
+
### Basic usage
|
16
|
+
|
17
|
+
```
|
18
|
+
Point = KtDataClass.create(:x, :y)
|
19
|
+
p1 = Point.new(x: 3, y: 4)
|
20
|
+
|
21
|
+
puts p1.x, p1.y
|
22
|
+
```
|
23
|
+
|
24
|
+
Also, some convenient methods can be added like below:
|
25
|
+
|
26
|
+
```
|
27
|
+
Point = KtDataClass.create(:x, :y) do
|
28
|
+
def -(other)
|
29
|
+
self.class.new(x: x - other.x, y: y - other.y)
|
30
|
+
end
|
31
|
+
|
32
|
+
def norm
|
33
|
+
Math.sqrt(x * x + y * y)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
(Point.new(x: 4, y: 5) - Point.new(x: 1, y: 1)).norm
|
38
|
+
# => 5.0
|
39
|
+
```
|
40
|
+
|
41
|
+
### Immutable
|
42
|
+
|
43
|
+
```
|
44
|
+
Profile = KtDataClass.create(:hobby)
|
45
|
+
profile = Profile.new(hobby: "Ruby")
|
46
|
+
|
47
|
+
profile.hobby = "Python"
|
48
|
+
# NoMethodError: undefined method `hobby=' for #<Profile:0x0000000000ee2668 @hobby="Ruby">
|
49
|
+
|
50
|
+
new_profile = profile.copy(hobby: "Python")
|
51
|
+
|
52
|
+
profile.hobby
|
53
|
+
# => "Ruby"
|
54
|
+
new_profile.hobby
|
55
|
+
# => "Python"
|
56
|
+
```
|
57
|
+
|
58
|
+
### Destructuring assignment
|
59
|
+
|
60
|
+
```
|
61
|
+
Point = KtDataClass.create(:x, :y)
|
62
|
+
p = Point.new(x: 1, y: 2)
|
63
|
+
x, y = p
|
64
|
+
|
65
|
+
x
|
66
|
+
# => 1
|
67
|
+
|
68
|
+
y
|
69
|
+
# => 2
|
70
|
+
```
|
71
|
+
|
72
|
+
## FAQ
|
73
|
+
|
74
|
+
### How can I define default value?
|
75
|
+
|
76
|
+
In Kotlin,
|
77
|
+
|
78
|
+
```
|
79
|
+
data class Point(x: Int = 0, y: Int = 0)
|
80
|
+
```
|
81
|
+
|
82
|
+
This can be implemented by overriding constructor like below:
|
83
|
+
|
84
|
+
```
|
85
|
+
Point = KtDataClass.create(:x, :y) do
|
86
|
+
def initialize(x: 0, y: 0)
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
Point.new
|
92
|
+
# => #<Point:0x00000000020c5b78 @x=0, @y=0>
|
93
|
+
|
94
|
+
Point.new(x: 1)
|
95
|
+
# => #<Point:0x00000000020a90e0 @x=1, @y=0>
|
96
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "kt_data_class"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "kt_data_class/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "kt_data_class"
|
8
|
+
spec.version = KtDataClass::VERSION
|
9
|
+
spec.authors = ["YusukeIwaki"]
|
10
|
+
spec.email = ["yusuke.iwaki@crowdworks.co.jp"]
|
11
|
+
|
12
|
+
spec.summary = "The Ruby porting of `data class` in Kotlin"
|
13
|
+
spec.homepage = "https://github.com/YusukeIwaki/kt_data_class"
|
14
|
+
|
15
|
+
# Specify which files should be added to the gem when it is released.
|
16
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
17
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
18
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.16"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
spec.add_development_dependency "rspec_junit_formatter"
|
28
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "kt_data_class/version"
|
2
|
+
require "kt_data_class/definition"
|
3
|
+
require "kt_data_class/factory"
|
4
|
+
|
5
|
+
module KtDataClass
|
6
|
+
def create(*attr_names, **kwargs, &block)
|
7
|
+
definition = Definition.new(attr_names, kwargs)
|
8
|
+
Factory.new(definition).create(&block)
|
9
|
+
end
|
10
|
+
|
11
|
+
module_function :create
|
12
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module KtDataClass
|
2
|
+
class Base
|
3
|
+
def initialize(*args, **kwargs)
|
4
|
+
unless args.empty?
|
5
|
+
raise ArgumentError.new("data class must be initialized with keyword arguments.")
|
6
|
+
end
|
7
|
+
|
8
|
+
(self.class.definition.keys - kwargs.keys).each do |missing_key|
|
9
|
+
raise ArgumentError.new("missing keyword: #{missing_key}")
|
10
|
+
end
|
11
|
+
(kwargs.keys - self.class.definition.keys).each do |unknown_key|
|
12
|
+
raise ArgumentError.new("unknown keyword: #{unknown_key}")
|
13
|
+
end
|
14
|
+
|
15
|
+
kwargs.each do |attr_name, value|
|
16
|
+
instance_variable_set("@#{attr_name}", value)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def copy(*args, **kwargs)
|
21
|
+
unless args.empty?
|
22
|
+
raise ArgumentError.new("parameters for copying must be keyword arguments.")
|
23
|
+
end
|
24
|
+
new_params = hash.merge(kwargs)
|
25
|
+
self.class.new(**new_params)
|
26
|
+
end
|
27
|
+
|
28
|
+
def hash
|
29
|
+
self.class.definition.keys.inject(Hash.new) do |h, attr_name|
|
30
|
+
h[attr_name] = instance_variable_get("@#{attr_name}")
|
31
|
+
h
|
32
|
+
end
|
33
|
+
end
|
34
|
+
alias_method :to_h, :hash
|
35
|
+
alias_method :to_hash, :hash
|
36
|
+
|
37
|
+
# @see http://maeharin.hatenablog.com/entry/20130107/p1
|
38
|
+
def to_ary
|
39
|
+
self.class.definition.keys.map do |attr_name|
|
40
|
+
instance_variable_get("@#{attr_name}")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
alias_method :to_a, :to_ary
|
44
|
+
|
45
|
+
def to_s
|
46
|
+
hash.to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
self.hash == other.hash
|
51
|
+
end
|
52
|
+
|
53
|
+
def eql?(other)
|
54
|
+
self.class == other.class && self.hash.eql?(other.hash)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module KtDataClass
|
2
|
+
class InvalidDefinitionError < ArgumentError ; end
|
3
|
+
|
4
|
+
class Definition
|
5
|
+
# @param [Array<Symbol>] attr_names
|
6
|
+
# @param [Hash] kwargs 空じゃないものを渡すと例外が発生する
|
7
|
+
def initialize(attr_names, kwargs = {})
|
8
|
+
raise_if_invalid(attr_names, kwargs)
|
9
|
+
@attr_names = attr_names
|
10
|
+
end
|
11
|
+
|
12
|
+
def keys
|
13
|
+
@attr_names
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def raise_if_invalid(attr_names, kwargs)
|
19
|
+
if !kwargs.empty? || !attr_names.is_a?(Array) || attr_names.any?{ |attr_name| !attr_name.is_a?(Symbol) }
|
20
|
+
raise InvalidDefinitionError.new("class definition must be specified with Array of Symbols")
|
21
|
+
end
|
22
|
+
duplicated_names = attr_names.group_by{|attr_name| attr_name}.values.select{|names| names.count >= 2}
|
23
|
+
unless duplicated_names.empty?
|
24
|
+
attr_names = duplicated_names.map(&:first)
|
25
|
+
raise InvalidDefinitionError.new("each argument is allowed to use only once. [#{attr_names.join(", ")}] has been used more than once.")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "kt_data_class/base"
|
2
|
+
|
3
|
+
module KtDataClass
|
4
|
+
class Factory
|
5
|
+
# @param [Definition] definition
|
6
|
+
def initialize(definition)
|
7
|
+
@definition = definition
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(&block)
|
11
|
+
member_definition = @definition
|
12
|
+
Class.new(Base) do
|
13
|
+
define_singleton_method(:definition) do
|
14
|
+
member_definition
|
15
|
+
end
|
16
|
+
attr_reader(*member_definition.keys)
|
17
|
+
|
18
|
+
unless block.nil?
|
19
|
+
class_eval(&block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kt_data_class
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- YusukeIwaki
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.16'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.16'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec_junit_formatter
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description:
|
70
|
+
email:
|
71
|
+
- yusuke.iwaki@crowdworks.co.jp
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".circleci/config.yml"
|
77
|
+
- ".gitignore"
|
78
|
+
- Gemfile
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- bin/console
|
82
|
+
- bin/setup
|
83
|
+
- kt_data_class.gemspec
|
84
|
+
- lib/kt_data_class.rb
|
85
|
+
- lib/kt_data_class/base.rb
|
86
|
+
- lib/kt_data_class/definition.rb
|
87
|
+
- lib/kt_data_class/factory.rb
|
88
|
+
- lib/kt_data_class/version.rb
|
89
|
+
homepage: https://github.com/YusukeIwaki/kt_data_class
|
90
|
+
licenses: []
|
91
|
+
metadata: {}
|
92
|
+
post_install_message:
|
93
|
+
rdoc_options: []
|
94
|
+
require_paths:
|
95
|
+
- lib
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
requirements: []
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 2.7.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 4
|
111
|
+
summary: The Ruby porting of `data class` in Kotlin
|
112
|
+
test_files: []
|