katachi 0.0.0.1 → 0.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 +4 -4
- data/.rubocop.yml +35 -1
- data/.tool-versions +1 -1
- data/Guardfile +17 -0
- data/README.md +296 -28
- data/Rakefile +14 -0
- data/cspell.config.yaml +19 -0
- data/docs/CONTRIBUTING.md +35 -0
- data/docs/HASH_COMPARISON_DESIGN.md +244 -0
- data/docs/PHILOSOPHY.md +43 -0
- data/lib/katachi/any_of.rb +36 -0
- data/lib/katachi/comparator/compare_array.rb +65 -0
- data/lib/katachi/comparator/compare_hash.rb +174 -0
- data/lib/katachi/comparator/compare_kv.rb +56 -0
- data/lib/katachi/comparator.rb +43 -0
- data/lib/katachi/comparison_result.rb +99 -0
- data/lib/katachi/predefined_shapes.rb +5 -0
- data/lib/katachi/rspec.rb +22 -0
- data/lib/katachi/shapes.rb +24 -0
- data/lib/katachi/version.rb +1 -1
- data/lib/katachi.rb +11 -2
- data/renovate.json +6 -0
- metadata +41 -16
- data/sig/katachi.rbs +0 -4
- /data/{CODE_OF_CONDUCT.md → docs/CODE_OF_CONDUCT.md} +0 -0
- /data/{SECURITY.md → docs/SECURITY.md} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eae61d46a9a766e14abce3b55525a693129e315439ed4984312b081ebf188ec7
|
4
|
+
data.tar.gz: fddee880cf7f3aeb4911b47b5143242dd5f38e1fd0308498b1e745f032b71898
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12e516977d0ad4fbb33b52920737b76bfc5fd218ce934fdd78691e16d38cd90ce62247fa89b6adc226bd0ae04cc6ece47c651c2ebd5c93e3fe0bef980fea2b9e
|
7
|
+
data.tar.gz: 76d6350536445901723c42df05b5cb2ed7bb15f781ee16896b03d88bc6cdae55a9e01bbf91a204776d820e4a3e9472eb7b0eaf5f88c34b3052ecdf7cd1fd25b1
|
data/.rubocop.yml
CHANGED
@@ -1,8 +1,42 @@
|
|
1
|
+
plugins:
|
2
|
+
- rubocop-performance
|
3
|
+
- rubocop-rake
|
4
|
+
- rubocop-rspec
|
5
|
+
|
1
6
|
AllCops:
|
2
|
-
TargetRubyVersion: 3.
|
7
|
+
TargetRubyVersion: 3.2
|
8
|
+
NewCops: enable
|
9
|
+
|
10
|
+
Layout/ClassStructure:
|
11
|
+
Enabled: true
|
12
|
+
|
13
|
+
Metrics/MethodLength:
|
14
|
+
CountAsOne: &count_as_one
|
15
|
+
- array
|
16
|
+
- hash
|
17
|
+
- heredoc
|
18
|
+
- method_call
|
19
|
+
|
20
|
+
RSpec/ExampleLength:
|
21
|
+
CountAsOne: *count_as_one
|
22
|
+
|
23
|
+
Style/ClassAndModuleChildren:
|
24
|
+
EnforcedStyle: compact
|
25
|
+
|
26
|
+
Style/EndlessMethod:
|
27
|
+
EnforcedStyle: require_single_line
|
28
|
+
|
29
|
+
Style/HashSyntax:
|
30
|
+
EnforcedShorthandSyntax: always
|
3
31
|
|
4
32
|
Style/StringLiterals:
|
5
33
|
EnforcedStyle: double_quotes
|
6
34
|
|
7
35
|
Style/StringLiteralsInInterpolation:
|
8
36
|
EnforcedStyle: double_quotes
|
37
|
+
|
38
|
+
Style/TrailingCommaInHashLiteral:
|
39
|
+
EnforcedStyleForMultiline: diff_comma
|
40
|
+
|
41
|
+
Style/TrailingCommaInArguments:
|
42
|
+
EnforcedStyleForMultiline: consistent_comma
|
data/.tool-versions
CHANGED
@@ -1 +1 @@
|
|
1
|
-
ruby 3.
|
1
|
+
ruby 3.2.7 3.3.7 3.4.2
|
data/Guardfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# More info at https://github.com/guard/guard#readme
|
4
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
5
|
+
require "guard/rspec/dsl"
|
6
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
7
|
+
|
8
|
+
# RSpec files
|
9
|
+
rspec = dsl.rspec
|
10
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
11
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
12
|
+
watch(rspec.spec_files)
|
13
|
+
|
14
|
+
# Ruby files
|
15
|
+
ruby = dsl.ruby
|
16
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
17
|
+
end
|
data/README.md
CHANGED
@@ -1,52 +1,320 @@
|
|
1
|
-
|
1
|
+

|
2
2
|
|
3
|
-
|
3
|
+
# Katachi
|
4
4
|
|
5
|
-
|
5
|
+
A tool for describing and validating objects as intuitively as possible.
|
6
6
|
|
7
|
-
|
7
|
+
```ruby
|
8
|
+
Katachi.compare(
|
9
|
+
value: {name: 'John', age: 30},
|
10
|
+
shape: {name: String, age: Integer}
|
11
|
+
).match? # => true
|
12
|
+
```
|
8
13
|
|
9
|
-
##
|
14
|
+
## What's with the name?
|
10
15
|
|
11
|
-
|
16
|
+
> The word “katachi” is a composite of “kata” (pattern) and “chi” (magical power), thus it includes meanings such as “complete form” or “form telling an attractive story.” It can reveal the relationship between shape, function and meaning.
|
17
|
+
>
|
18
|
+
> https://symmetry-us.com/about_the_site/what-is-katachi/
|
12
19
|
|
13
|
-
|
20
|
+
This tool is all about defining the shape of your data. The usual words of schema, definition, or validator all felt too formal. Since Ruby originated in Japan, I looked up the Japanese word for shape. It came back as 形 (katachi), and the above quote was the first thing I saw when checking for prior usage. It felt like a perfect fit.
|
14
21
|
|
15
|
-
|
22
|
+
## Features
|
16
23
|
|
17
|
-
|
24
|
+
### Basic Shape Matching
|
18
25
|
|
19
|
-
|
26
|
+
A comparison system built on the power of the Ruby `===` operator.
|
20
27
|
|
21
|
-
|
28
|
+
```ruby
|
29
|
+
Kt = Katachi
|
30
|
+
Kt.compare(value: 'hello', shape: 'hello').match? # => true
|
31
|
+
Kt.compare(value: 'hello', shape: 'world').match? # => false
|
32
|
+
Kt.compare(value: 'hello', shape: String).match? # => true
|
33
|
+
Kt.compare(value: 'hello', shape: /ell/).match? # => true
|
34
|
+
Kt.compare(value: 4, shape: 1..10).match? # => true
|
35
|
+
Kt.compare(value: 4, shape: ->(v) { v > 3 }).match? # => true
|
36
|
+
```
|
22
37
|
|
23
|
-
|
38
|
+
If you're dealing with more variable data, there's`any_of` to allow multiple types.
|
39
|
+
This is especially useful for optional values, since we treat `nil` just like any other value.
|
24
40
|
|
25
|
-
|
41
|
+
```ruby
|
42
|
+
value = user.preferred_name
|
43
|
+
shape = Kt.any_of(String, nil)
|
44
|
+
Kt.compare(value:, shape:).match? # => true
|
45
|
+
```
|
26
46
|
|
27
|
-
|
47
|
+
### An Easy-To-Use Shape Library
|
28
48
|
|
29
|
-
|
49
|
+
We provide some common shapes that can be accessed by `:${name}`.
|
30
50
|
|
31
|
-
|
51
|
+
```ruby
|
52
|
+
Kt.compare(
|
53
|
+
value: "123e4567-e89b-12d3-a456-426614174000",
|
54
|
+
shape: :$uuid
|
55
|
+
).match? # => true
|
56
|
+
```
|
32
57
|
|
33
|
-
|
58
|
+
You can also add your own shapes to fit your needs.
|
34
59
|
|
35
|
-
|
60
|
+
```ruby
|
61
|
+
Kt.add_shape(:$even, ->(v) { v.even? })
|
62
|
+
Kt.compare(value: 4, shape: :$even).match? # => true
|
63
|
+
```
|
36
64
|
|
37
|
-
The
|
65
|
+
The full list of included shapes can be found in the [predefined_shapes.rb](./lib/katachi/predefined_shapes.rb) file.
|
66
|
+
If you think there's a shape everyone should have, feel free to open an issue! Or better yet, a PR!
|
67
|
+
|
68
|
+
### Array Comparison
|
69
|
+
|
70
|
+
Arrays are checked to ensure their contents also match the shape.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Kt.compare(value: [1], shape: [Integer]).match? # => true
|
74
|
+
```
|
75
|
+
|
76
|
+
Since arrays aren't usually a fixed length, we don't compare the length
|
77
|
+
of the value and shape arrays. Instead, we treat the contents of the shape
|
78
|
+
array like `any_of`.
|
79
|
+
`[String, Integer]` is effectively shorthand for `[Kt.any_of(String, Integer)]`.
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
# pseudo-code for how arrays are compared
|
83
|
+
array_matches = value.all? do |element|
|
84
|
+
shape.any? do |shape_element|
|
85
|
+
Kt.compare(value: element, shape: shape_element).match?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
89
|
+
|
90
|
+
Seeing a few examples is probably the best way to understand how this works.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
Kt.compare(value: [1, 2, 3, 4, 5], shape: [Integer]).match? # => true
|
94
|
+
Kt.compare(value: ['a', 'b', 'c'], shape: [Integer]).match? # => false
|
95
|
+
Kt.compare(value: [1, 2, 'c'], shape: [Integer]).match? # => false
|
96
|
+
Kt.compare(value: ['a', 2, 'c', 4], shape: [Integer, String]).match? # => true
|
97
|
+
```
|
98
|
+
|
99
|
+
We said arrays aren't _usually_ a fixed length but it does happen.
|
100
|
+
|
101
|
+
For this situation, the Ruby `in` operator is your friend.
|
102
|
+
|
103
|
+
Here's how you can check for an array of exactly 5 elements without a lot of typing.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
value = [1, 2, 3, 4, 5]
|
107
|
+
shape = ->(v) { v in ([Integer] * 5) }
|
108
|
+
Kt.compare(value:, shape:).match? # => true
|
109
|
+
```
|
110
|
+
|
111
|
+
It also works for when you want to check for specific values at specific indexes.
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
value = [1, 'a', 2]
|
115
|
+
shape = ->(v) { v in [Integer, String, Integer] }
|
116
|
+
Kt.compare(value:, shape:).match? # => true
|
117
|
+
```
|
118
|
+
|
119
|
+
Checks are recursive, so you can nest arrays as deep as you like.
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
value = [1, [2, [3, 4]]]
|
123
|
+
shape = [Integer, [Integer, [Integer]]]
|
124
|
+
Kt.compare(value:, shape:).match? # => true
|
125
|
+
```
|
126
|
+
|
127
|
+
### Hash Comparison
|
128
|
+
|
129
|
+
Hashes are checked to ensure their keys and values match the shape.
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
value = {a: 1}
|
133
|
+
shape = {a: Integer}
|
134
|
+
Kt.compare(value:, shape:).match? # => true
|
135
|
+
```
|
136
|
+
|
137
|
+
By default, no extra or missing hash keys are allowed.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
# This will fail because `:b` is not in the shape
|
141
|
+
value = {a: 1, b: 2}
|
142
|
+
shape = {a: Integer}
|
143
|
+
Kt.compare(value:, shape:).match? # => false
|
144
|
+
|
145
|
+
# This will fail because `:b` is missing from the value
|
146
|
+
value = {a: 1}
|
147
|
+
shape = {a: Integer, b: String}
|
148
|
+
Kt.compare(value:, shape:).match? # => false
|
149
|
+
```
|
150
|
+
|
151
|
+
If you want to allow extra keys, no special syntax is needed.
|
152
|
+
Ruby comes to the rescue!
|
153
|
+
Ruby accepts more than just strings and symbols as hash keys.
|
154
|
+
We take advantage of this by applying the same comparison logic to the keys as we do to the values.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
value = {a: 1, b: 2, c: 3}
|
158
|
+
shape = {a: Integer, Symbol => Integer}
|
159
|
+
Kt.compare(value:, shape:).match? # => true
|
160
|
+
```
|
161
|
+
|
162
|
+
This means you can use any shape you like for the keys, though it's usually best to stick to simple shapes.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
value = { "123e4567-e89b-12d3-a456-426614174000" => "My Id" }
|
166
|
+
shape = { :$uuid => String}
|
167
|
+
Kt.compare(value:, shape:).match? # => true
|
168
|
+
```
|
169
|
+
|
170
|
+
We've made sure that if you go through the trouble of describing an exact key, it will override more generic matches.
|
171
|
+
We consider an exact key to be one that doesn't contain a Class, a Range, a Proc, or a Regexp.
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
value = {a: 'a', b: 'b', c: 'c'}
|
175
|
+
shape = {a: 'foo', Symbol => String}
|
176
|
+
Kt.compare(value:, shape:).match? # => false
|
177
|
+
```
|
178
|
+
|
179
|
+
For making keys optional, we provide a special `:$undefined` shape.
|
180
|
+
|
181
|
+
```ruby
|
182
|
+
value = {a: 1}
|
183
|
+
shape = {a: Integer, b: Kt.any_of(Integer, :$undefined)}
|
184
|
+
Kt.compare(value:, shape:).match? # => true
|
185
|
+
```
|
38
186
|
|
39
|
-
|
187
|
+
As with arrays, hashes can be nested as deep as you like.
|
40
188
|
|
41
|
-
|
189
|
+
```ruby
|
190
|
+
value = {a: {b: {c: 1}}}
|
191
|
+
shape = {a: {b: {c: Integer}}}
|
192
|
+
Kt.compare(value:, shape:).match? # => true
|
193
|
+
```
|
42
194
|
|
43
|
-
|
195
|
+
### Custom Comparisons
|
44
196
|
|
45
|
-
|
197
|
+
Need something more complex? Just add a `kt_compare` class method to whatever you'd like to compare.
|
198
|
+
As long as it returns a `Katachi::Result`, you're good to go!
|
46
199
|
|
47
|
-
|
200
|
+
```ruby
|
201
|
+
class CanRideThisRollerCoaster
|
202
|
+
def self.kt_compare(value:)
|
203
|
+
age_check = Kt.compare(value: value.age, shape: 14..)
|
204
|
+
height_check = Kt.compare(value: value.height, shape: 42..123)
|
205
|
+
has_parent_check = Kt.compare(value: value.has_parent, shape: true)
|
206
|
+
is_allowed = height_check.match? && (age_check.match? || has_parent_check.match?)
|
207
|
+
Kt::Result.new(
|
208
|
+
value:,
|
209
|
+
shape: self,
|
210
|
+
code: is_allowed ? :match : :mismatch,
|
211
|
+
child_results: {age_check:, height_check:, has_parent_check:}
|
212
|
+
)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
```
|
48
216
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
217
|
+
### RSpec Integration
|
218
|
+
|
219
|
+
When using Rspec, the way it turns question mark methods in to `be_` methods is a perfect fit for our `match?` method.
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
# The following two lines are equivalent
|
223
|
+
expect(Kt.compare('abc', 'abc').match?).to be true
|
224
|
+
expect(Kt.compare('abc', 'abc')).to be_match
|
225
|
+
```
|
226
|
+
|
227
|
+
For when you don't want a match, RSpec has a helpful utility for defining the opposite of a matcher.
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
RSpec::Matchers.define_negated_matcher :be_mismatch, :be_match
|
231
|
+
expect(Kt.compare('abc', 123)).to be_mismatch
|
232
|
+
```
|
233
|
+
|
234
|
+
We've also added RSpec matchers to make testing your shapes even easier.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
require 'katachi/rspec'
|
238
|
+
|
239
|
+
expect(Kt.compare('abc', 123)).to have_compare_code(:mismatch)
|
240
|
+
expect('abc').to have_shape(String)
|
241
|
+
expect('abc').to have_shape('abc').with_code(:exact_match)
|
242
|
+
```
|
243
|
+
|
244
|
+
### Detailed Diagnostics
|
245
|
+
|
246
|
+
All comparisons return a `Katachi::Result` object that contains detailed information about the comparison.
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
value = {a: 1, foo: :bar}
|
250
|
+
shape = { a: Integer, foo: String }
|
251
|
+
result = Kt.compare(value:, shape:)
|
252
|
+
result.match? # => false
|
253
|
+
result.code # => :hash_is_mismatch
|
254
|
+
result.child_results # contains the recursive results of interior comparisons
|
255
|
+
result.to_s == <<~RESULT.chomp
|
256
|
+
:hash_is_mismatch <-- compare(value: {a: 1, foo: :bar}, shape: {a: Integer, foo: String})
|
257
|
+
:hash_has_no_missing_keys <-- compare(value: {a: 1, foo: :bar}, shape: {a: Integer, foo: String}); child_label: :$required_keys
|
258
|
+
:hash_key_exact_match <-- compare(value: :a, shape: :a); child_label: :a
|
259
|
+
:hash_key_exact_match <-- compare(value: :foo, shape: :foo); child_label: :foo
|
260
|
+
:hash_has_no_extra_keys <-- compare(value: {a: 1, foo: :bar}, shape: {a: Integer, foo: String}); child_label: :$extra_keys
|
261
|
+
:hash_key_exactly_allowed <-- compare(value: :a, shape: :a); child_label: :a
|
262
|
+
:hash_key_exactly_allowed <-- compare(value: :foo, shape: :foo); child_label: :foo
|
263
|
+
:hash_values_are_mismatch <-- compare(value: {a: 1, foo: :bar}, shape: {a: Integer, foo: String}); child_label: :$values
|
264
|
+
:kv_specific_match <-- compare(value: {a: 1}, shape: {a: Integer}); child_label: [:a, 1]
|
265
|
+
:match <-- compare(value: 1, shape: Integer); child_label: Integer
|
266
|
+
:kv_specific_mismatch <-- compare(value: {foo: :bar}, shape: {foo: String}); child_label: [:foo, :bar]
|
267
|
+
:mismatch <-- compare(value: :bar, shape: String); child_label: String
|
268
|
+
RESULT
|
269
|
+
```
|
270
|
+
|
271
|
+
## Future Features Under Consideration
|
272
|
+
|
273
|
+
- [ ] More shapes (e.g. `:$email`, `:$url`, `:$iso_8601`)
|
274
|
+
- [ ] More "matching modifiers" (e.g. `all_of`, `one_of`, `none_of`)
|
275
|
+
- [ ] Docusaurus github pages for documentation
|
276
|
+
- [ ] More output formats (e.g. `to_json`, `to_hash`, etc...)
|
277
|
+
- [ ] Custom shape codes (e.g. `:email_is_invalid`)
|
278
|
+
- [ ] Minitest integration
|
279
|
+
- [ ] Rails integration (e.g. `validates_shape_of`)
|
280
|
+
- [ ] Shape-to-TypeScript conversion
|
281
|
+
- [ ] Shape-to-Zod conversion
|
282
|
+
- [ ] Shape-to-OpenAPI conversion
|
283
|
+
- [ ] Recursive shape definitions (e.g. `:$user => {name: String, spouse: Kt.any_of(:$user, nil)}`)
|
284
|
+
- [ ] `katachi-rspec-api` for testing+documenting APIs in a way inspired [RSwag](https://github.com/rswag/rswag)
|
285
|
+
|
286
|
+
## Installation
|
287
|
+
|
288
|
+
Install the gem and add to the application's Gemfile by executing:
|
289
|
+
|
290
|
+
```bash
|
291
|
+
$ bundle add katachi
|
292
|
+
```
|
293
|
+
|
294
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
295
|
+
|
296
|
+
```bash
|
297
|
+
$ gem install katachi
|
298
|
+
```
|
299
|
+
|
300
|
+
## Inspiration
|
301
|
+
|
302
|
+
This is inspired by my experiences testing using [RSwag](https://github.com/rswag/rswag) and from my small part in helping maintain it. I wasn't happy with how often I had to look up the OpenAPI spec to be able to follow it.
|
303
|
+
|
304
|
+
A lot of this came down to OpenAPI itself being complex and making significant changes over the years (e.g. `x-nullable: true` → `nullable: true` → `type: ["string", "null"]`). A bigger part is they're limited to valid JSON, so they have very few tools to work with.
|
305
|
+
|
306
|
+
I started wondering if I could tweak RSwag to smooth over some of these rough edges. Is there a way to make it easier to write and harder to mess up?
|
307
|
+
|
308
|
+
It started as consolidating a few helper functions together, before a bigger question hit me:
|
309
|
+
|
310
|
+
**“What if I ditched writing OpenAPI entirely?”**
|
311
|
+
|
312
|
+
Rather than drag all of their maintainers and users along with my crackpot schemes, I decided it was time to set off on a new project: `Katachi`
|
313
|
+
|
314
|
+
## Development and Contributing
|
315
|
+
|
316
|
+
See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for information on how to contribute to Katachi.
|
317
|
+
|
318
|
+
## License
|
319
|
+
|
320
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -10,3 +10,17 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
desc "Opens a console with Katachi loaded for easier experimentation"
|
15
|
+
task :console do
|
16
|
+
require "bundler/setup"
|
17
|
+
require "irb"
|
18
|
+
require "katachi"
|
19
|
+
ARGV.clear
|
20
|
+
IRB.start(__FILE__)
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "All the steps necessary to get the project ready for development"
|
24
|
+
task :setup do
|
25
|
+
system "bundle install"
|
26
|
+
end
|
data/cspell.config.yaml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
$schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json
|
2
|
+
version: "0.2"
|
3
|
+
words:
|
4
|
+
- bindir
|
5
|
+
- diffable
|
6
|
+
- kata
|
7
|
+
- Katachi
|
8
|
+
- kwargs
|
9
|
+
- PEBKAC
|
10
|
+
- pipefail
|
11
|
+
- popen
|
12
|
+
- precompare
|
13
|
+
- procs
|
14
|
+
- Rakefile
|
15
|
+
- readlines
|
16
|
+
- rubocop
|
17
|
+
- rubygems
|
18
|
+
- shapeables
|
19
|
+
- Tannas
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Philosophy
|
4
|
+
|
5
|
+
To guide development and explain "why was it done this way?" we have a [Philosophy](./PHILOSOPHY.md) document.
|
6
|
+
Contributions should aim to follow the principles outlined there.
|
7
|
+
|
8
|
+
## Development
|
9
|
+
|
10
|
+
After checking out the repo, run `rake setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `rake console` for an interactive prompt that will allow you to experiment.
|
11
|
+
|
12
|
+
In general, we want the `Rakefile` to be the source of truth for all tasks. If you find yourself manually running a task more than once, consider adding it to the `Rakefile`.
|
13
|
+
|
14
|
+
We don't have a release process yet, but it's coming soon!
|
15
|
+
|
16
|
+
## Contributing
|
17
|
+
|
18
|
+
Bug reports and pull requests are welcome. Feature requests are welcome, but please open an issue first to discuss what you would like to change.
|
19
|
+
|
20
|
+
Everyone interacting in the Katachi project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](./CODE_OF_CONDUCT.md).
|
21
|
+
|
22
|
+
# Versioning
|
23
|
+
|
24
|
+
This gem uses [Epoch Semantic Versioning](https://antfu.me/posts/epoch-semver).
|
25
|
+
|
26
|
+
The format is: `EPOCH.MAJOR.MINOR.PATCH`
|
27
|
+
|
28
|
+
> - EPOCH: Increment when you make significant or groundbreaking changes.
|
29
|
+
> - MAJOR: Increment when you make minor incompatible API changes.
|
30
|
+
> - MINOR: Increment when you add functionality in a backwards-compatible manner.
|
31
|
+
> - PATCH: Increment when you make backwards-compatible bug fixes.
|
32
|
+
|
33
|
+
Until we reach EPOCH 1, we will be in a state of rapid development.
|
34
|
+
Breaking changes will still be communicated via major versions, but
|
35
|
+
they may be fairly large in scope and number.
|