flows 0.2.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/{build.yml → test.yml} +5 -10
  3. data/.gitignore +9 -1
  4. data/.mdlrc +1 -1
  5. data/.reek.yml +54 -0
  6. data/.rubocop.yml +26 -7
  7. data/.rubocop_todo.yml +27 -0
  8. data/.ruby-version +1 -1
  9. data/.yardopts +1 -0
  10. data/CHANGELOG.md +81 -0
  11. data/Gemfile +0 -6
  12. data/README.md +167 -363
  13. data/Rakefile +35 -1
  14. data/bin/.rubocop.yml +5 -0
  15. data/bin/all_the_errors +55 -0
  16. data/bin/benchmark +73 -105
  17. data/bin/benchmark_cli/compare.rb +118 -0
  18. data/bin/benchmark_cli/compare/a_plus_b.rb +22 -0
  19. data/bin/benchmark_cli/compare/base.rb +45 -0
  20. data/bin/benchmark_cli/compare/command.rb +47 -0
  21. data/bin/benchmark_cli/compare/ten_steps.rb +22 -0
  22. data/bin/benchmark_cli/examples.rb +23 -0
  23. data/bin/benchmark_cli/examples/.rubocop.yml +22 -0
  24. data/bin/benchmark_cli/examples/a_plus_b/dry_do.rb +23 -0
  25. data/bin/benchmark_cli/examples/a_plus_b/dry_transaction.rb +17 -0
  26. data/bin/benchmark_cli/examples/a_plus_b/flows_do.rb +22 -0
  27. data/bin/benchmark_cli/examples/a_plus_b/flows_railway.rb +13 -0
  28. data/bin/benchmark_cli/examples/a_plus_b/flows_scp.rb +13 -0
  29. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_mut.rb +13 -0
  30. data/bin/benchmark_cli/examples/a_plus_b/flows_scp_oc.rb +21 -0
  31. data/bin/benchmark_cli/examples/a_plus_b/trailblazer.rb +15 -0
  32. data/bin/benchmark_cli/examples/ten_steps/dry_do.rb +70 -0
  33. data/bin/benchmark_cli/examples/ten_steps/dry_transaction.rb +64 -0
  34. data/bin/benchmark_cli/examples/ten_steps/flows_do.rb +69 -0
  35. data/bin/benchmark_cli/examples/ten_steps/flows_railway.rb +58 -0
  36. data/bin/benchmark_cli/examples/ten_steps/flows_scp.rb +58 -0
  37. data/bin/benchmark_cli/examples/ten_steps/flows_scp_mut.rb +58 -0
  38. data/bin/benchmark_cli/examples/ten_steps/flows_scp_oc.rb +66 -0
  39. data/bin/benchmark_cli/examples/ten_steps/trailblazer.rb +60 -0
  40. data/bin/benchmark_cli/helpers.rb +12 -0
  41. data/bin/benchmark_cli/ruby.rb +15 -0
  42. data/bin/benchmark_cli/ruby/command.rb +38 -0
  43. data/bin/benchmark_cli/ruby/method_exec.rb +71 -0
  44. data/bin/benchmark_cli/ruby/self_class.rb +69 -0
  45. data/bin/benchmark_cli/ruby/structs.rb +90 -0
  46. data/bin/console +1 -0
  47. data/bin/docserver +7 -0
  48. data/bin/errors +138 -0
  49. data/bin/errors_cli/contract_error_demo.rb +49 -0
  50. data/bin/errors_cli/di_error_demo.rb +38 -0
  51. data/bin/errors_cli/flow_error_demo.rb +22 -0
  52. data/bin/errors_cli/flows_router_error_demo.rb +15 -0
  53. data/bin/errors_cli/interface_error_demo.rb +17 -0
  54. data/bin/errors_cli/oc_error_demo.rb +40 -0
  55. data/bin/errors_cli/railway_error_demo.rb +10 -0
  56. data/bin/errors_cli/result_error_demo.rb +13 -0
  57. data/bin/errors_cli/scp_error_demo.rb +17 -0
  58. data/docs/README.md +3 -187
  59. data/docs/_sidebar.md +0 -24
  60. data/docs/index.html +1 -1
  61. data/flows.gemspec +27 -2
  62. data/forspell.dict +9 -0
  63. data/lefthook.yml +9 -0
  64. data/lib/flows.rb +11 -5
  65. data/lib/flows/contract.rb +402 -0
  66. data/lib/flows/contract/array.rb +55 -0
  67. data/lib/flows/contract/case_eq.rb +43 -0
  68. data/lib/flows/contract/compose.rb +77 -0
  69. data/lib/flows/contract/either.rb +53 -0
  70. data/lib/flows/contract/error.rb +24 -0
  71. data/lib/flows/contract/hash.rb +75 -0
  72. data/lib/flows/contract/hash_of.rb +70 -0
  73. data/lib/flows/contract/helpers.rb +22 -0
  74. data/lib/flows/contract/predicate.rb +34 -0
  75. data/lib/flows/contract/transformer.rb +50 -0
  76. data/lib/flows/contract/tuple.rb +70 -0
  77. data/lib/flows/flow.rb +96 -7
  78. data/lib/flows/flow/errors.rb +29 -0
  79. data/lib/flows/flow/node.rb +132 -0
  80. data/lib/flows/flow/router.rb +29 -0
  81. data/lib/flows/flow/router/custom.rb +59 -0
  82. data/lib/flows/flow/router/errors.rb +11 -0
  83. data/lib/flows/flow/router/simple.rb +25 -0
  84. data/lib/flows/plugin.rb +15 -0
  85. data/lib/flows/plugin/dependency_injector.rb +170 -0
  86. data/lib/flows/plugin/dependency_injector/dependency.rb +24 -0
  87. data/lib/flows/plugin/dependency_injector/dependency_definition.rb +16 -0
  88. data/lib/flows/plugin/dependency_injector/dependency_list.rb +55 -0
  89. data/lib/flows/plugin/dependency_injector/errors.rb +58 -0
  90. data/lib/flows/plugin/implicit_init.rb +45 -0
  91. data/lib/flows/plugin/interface.rb +84 -0
  92. data/lib/flows/plugin/output_contract.rb +85 -0
  93. data/lib/flows/plugin/output_contract/dsl.rb +48 -0
  94. data/lib/flows/plugin/output_contract/errors.rb +74 -0
  95. data/lib/flows/plugin/output_contract/wrapper.rb +55 -0
  96. data/lib/flows/plugin/profiler.rb +114 -0
  97. data/lib/flows/plugin/profiler/injector.rb +35 -0
  98. data/lib/flows/plugin/profiler/report.rb +48 -0
  99. data/lib/flows/plugin/profiler/report/events.rb +43 -0
  100. data/lib/flows/plugin/profiler/report/flat.rb +41 -0
  101. data/lib/flows/plugin/profiler/report/flat/method_report.rb +80 -0
  102. data/lib/flows/plugin/profiler/report/raw.rb +15 -0
  103. data/lib/flows/plugin/profiler/report/tree.rb +98 -0
  104. data/lib/flows/plugin/profiler/report/tree/calculated_node.rb +116 -0
  105. data/lib/flows/plugin/profiler/report/tree/node.rb +34 -0
  106. data/lib/flows/plugin/profiler/wrapper.rb +53 -0
  107. data/lib/flows/railway.rb +140 -34
  108. data/lib/flows/railway/dsl.rb +8 -18
  109. data/lib/flows/railway/errors.rb +8 -12
  110. data/lib/flows/railway/step.rb +24 -0
  111. data/lib/flows/railway/step_list.rb +38 -0
  112. data/lib/flows/result.rb +188 -2
  113. data/lib/flows/result/do.rb +158 -16
  114. data/lib/flows/result/err.rb +12 -6
  115. data/lib/flows/result/errors.rb +29 -17
  116. data/lib/flows/result/helpers.rb +25 -3
  117. data/lib/flows/result/ok.rb +12 -6
  118. data/lib/flows/shared_context_pipeline.rb +342 -0
  119. data/lib/flows/shared_context_pipeline/dsl.rb +12 -0
  120. data/lib/flows/shared_context_pipeline/dsl/callbacks.rb +35 -0
  121. data/lib/flows/shared_context_pipeline/dsl/tracks.rb +52 -0
  122. data/lib/flows/shared_context_pipeline/errors.rb +17 -0
  123. data/lib/flows/shared_context_pipeline/mutation_step.rb +30 -0
  124. data/lib/flows/shared_context_pipeline/router_definition.rb +21 -0
  125. data/lib/flows/shared_context_pipeline/step.rb +55 -0
  126. data/lib/flows/shared_context_pipeline/track.rb +54 -0
  127. data/lib/flows/shared_context_pipeline/track_list.rb +51 -0
  128. data/lib/flows/shared_context_pipeline/wrap.rb +73 -0
  129. data/lib/flows/util.rb +17 -0
  130. data/lib/flows/util/inheritable_singleton_vars.rb +86 -0
  131. data/lib/flows/util/inheritable_singleton_vars/dup_strategy.rb +100 -0
  132. data/lib/flows/util/inheritable_singleton_vars/isolation_strategy.rb +91 -0
  133. data/lib/flows/util/prepend_to_class.rb +191 -0
  134. data/lib/flows/version.rb +1 -1
  135. metadata +253 -38
  136. data/Gemfile.lock +0 -174
  137. data/bin/demo +0 -66
  138. data/bin/examples.rb +0 -195
  139. data/bin/profile_10steps +0 -106
  140. data/bin/ruby_benchmarks +0 -26
  141. data/docs/CNAME +0 -1
  142. data/docs/contributing/benchmarks_profiling.md +0 -3
  143. data/docs/contributing/local_development.md +0 -3
  144. data/docs/flow/direct_usage.md +0 -3
  145. data/docs/flow/general_idea.md +0 -3
  146. data/docs/operation/basic_usage.md +0 -1
  147. data/docs/operation/inject_steps.md +0 -3
  148. data/docs/operation/lambda_steps.md +0 -3
  149. data/docs/operation/result_shapes.md +0 -3
  150. data/docs/operation/routing_tracks.md +0 -3
  151. data/docs/operation/wrapping_steps.md +0 -3
  152. data/docs/overview/performance.md +0 -336
  153. data/docs/railway/basic_usage.md +0 -232
  154. data/docs/result_objects/basic_usage.md +0 -196
  155. data/docs/result_objects/do_notation.md +0 -139
  156. data/lib/flows/node.rb +0 -27
  157. data/lib/flows/operation.rb +0 -52
  158. data/lib/flows/operation/builder.rb +0 -130
  159. data/lib/flows/operation/builder/build_router.rb +0 -37
  160. data/lib/flows/operation/dsl.rb +0 -93
  161. data/lib/flows/operation/errors.rb +0 -75
  162. data/lib/flows/operation/executor.rb +0 -78
  163. data/lib/flows/railway/builder.rb +0 -68
  164. data/lib/flows/railway/executor.rb +0 -23
  165. data/lib/flows/result_router.rb +0 -14
  166. data/lib/flows/router.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eed3e3ba742be2b25c1ae3ca7b52d2db3418183e0950616409ef8d05232365de
4
- data.tar.gz: c2f340f63e6e55178426c28fcfe29aa6d6c9af7d9a4b539d43461f068f4a6082
3
+ metadata.gz: dd10b19c7b749fd46c6b8897db3c9912603b4111af7793449d5ff49f69adfcb1
4
+ data.tar.gz: b18271b7321f199e978ebd4f66f2d21e532c20dd37cd61b6b641b00d875c2f4c
5
5
  SHA512:
6
- metadata.gz: 44a6ac9cdcedb674aab92373647582e038602c9459361d1ccbc6502cd78556835042fb81004c2329f1c1a95108371040dddb829fbd11a8eaa20bf310817163f3
7
- data.tar.gz: 333ff1f85e39a794c5e4073e7deeca887562a0568497cd480d75b26338aff242dc8c5d1a8f67ce822083e894beecfe033b7abcffe25e931490de53db7e52cf29
6
+ metadata.gz: 6b3e90d9b76911f1e0a0539bd89eee76a68dcba04f4136858412e4d0e0e1ca51ff8f35027963ef52fdd870f2b74ffe9a326aca4802cd443af2e55f8bf18f67b1
7
+ data.tar.gz: 5a6306a15a6024a1c9412f325ab3454b029abcd7d80defee6a9d5b286e8664981b572fb0ecf6f91d06b98c7d5f3040169fe02aae43b8ee8261fa7b65c469fe54
@@ -1,4 +1,4 @@
1
- name: Build
1
+ name: Test
2
2
 
3
3
  on:
4
4
  push:
@@ -11,7 +11,7 @@ on:
11
11
  - cron: 0 2 * * 1-5
12
12
 
13
13
  jobs:
14
- build:
14
+ test:
15
15
  runs-on: ubuntu-latest
16
16
  strategy:
17
17
  fail-fast: false
@@ -19,6 +19,7 @@ jobs:
19
19
  ruby:
20
20
  - 2.5.x
21
21
  - 2.6.x
22
+ # - 2.7.x
22
23
  steps:
23
24
  - uses: actions/checkout@v1
24
25
  - name: Set up Ruby
@@ -31,13 +32,7 @@ jobs:
31
32
  run: sudo apt-get install hunspell
32
33
  - name: Install Deps
33
34
  run: bundle install --jobs 4 --retry 3
34
- - name: Rubocop
35
- run: bundle exec rubocop
36
- - name: RSpec
35
+ - name: Test
37
36
  env:
38
37
  CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
39
- run: bundle exec rspec
40
- - name: Markdown Linter (docs)
41
- run: bundle exec mdl docs/**/*.md README.md
42
- - name: Spelling Check (docs & code)
43
- run: bundle exec forspell
38
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  /.bundle/
2
+ /.vendor/
2
3
  /.yardoc
3
4
  /_yardoc/
4
5
  /coverage/
@@ -12,4 +13,11 @@
12
13
 
13
14
  # profile results folder
14
15
  /profile/
15
- !/profile/.keep
16
+ !/profile/.keep
17
+
18
+ # OS specific stuff
19
+ .DS_Store
20
+
21
+ # In case of gem development we don't need to track Gemfile.lock
22
+ # All the constraints must be expressed in gemspec
23
+ /Gemfile.lock
data/.mdlrc CHANGED
@@ -1 +1 @@
1
- rules "~MD013", "~MD033"
1
+ rules "~MD013", "~MD033", "~MD024"
@@ -0,0 +1,54 @@
1
+ # default config: https://github.com/troessner/reek/blob/master/docs/defaults.reek.yml
2
+ # detectors' docs: https://github.com/troessner/reek/tree/master/docs
3
+ ---
4
+ exclude_paths:
5
+ - 'bin/'
6
+ - 'spec/'
7
+
8
+ detectors:
9
+ LongParameterList:
10
+ enabled: true
11
+ exclude: []
12
+ max_params: 4 # +1 to default value
13
+ overrides:
14
+ initialize:
15
+ max_params: 6 # +1 to default value
16
+ ModuleInitialize:
17
+ enabled: false
18
+ TooManyInstanceVariables:
19
+ enabled: true
20
+ exclude: []
21
+ max_instance_variables: 5 # +1 to default value
22
+ DataClump:
23
+ exclude:
24
+ - Flows::Result::Helpers
25
+ - Flows::SharedContextPipeline::DSL::Tracks
26
+ IrresponsibleModule:
27
+ exclude:
28
+ - Flows::SharedContextPipeline
29
+ FeatureEnvy:
30
+ exclude:
31
+ - Flows::SharedContextPipeline#call
32
+ - Flows::Contract # too many false positives here
33
+ - Flows::Plugin::Profiler::Report#events
34
+ TooManyStatements:
35
+ exclude:
36
+ - Flows::SharedContextPipeline#call
37
+ - Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
38
+ - Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
39
+ - initialize
40
+ DuplicateMethodCall:
41
+ exclude:
42
+ - Flows::SharedContextPipeline#call
43
+ - Flows::Plugin::Profiler::Wrapper#make_instance_wrapper
44
+ - Flows::Plugin::Profiler::Wrapper#make_singleton_wrapper
45
+ - 'length'
46
+ MissingSafeMethod:
47
+ exclude:
48
+ - Flows::Contract
49
+ BooleanParameter:
50
+ exclude:
51
+ - Flows::Plugin::OutputContract::DSL
52
+ TooManyMethods:
53
+ exclude:
54
+ - Flows::Plugin::Profiler::Report::Tree::Node
@@ -1,12 +1,24 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  require:
2
4
  - rubocop-rspec
3
5
  - rubocop-performance
4
6
  - rubocop-md
5
7
 
6
8
  AllCops:
7
- TargetRubyVersion: 2.6
9
+ TargetRubyVersion: 2.5
10
+ NewCops: enable
11
+
12
+ Style/HashEachMethods:
13
+ Enabled: true
14
+
15
+ Style/HashTransformKeys:
16
+ Enabled: true
17
+
18
+ Style/HashTransformValues:
19
+ Enabled: true
8
20
 
9
- LineLength:
21
+ Layout/LineLength:
10
22
  Max: 120
11
23
 
12
24
  Metrics/BlockLength:
@@ -22,9 +34,10 @@ Metrics/ParameterLists:
22
34
  Style/WhileUntilModifier:
23
35
  Enabled: false
24
36
 
25
- Naming/UncommunicativeMethodParamName:
37
+ Naming/MethodParameterName:
26
38
  Exclude:
27
39
  - '**/*.md'
40
+ - 'spec/**/*'
28
41
 
29
42
  Style/MixinUsage:
30
43
  Exclude:
@@ -38,10 +51,16 @@ Lint/UnusedBlockArgument:
38
51
  Exclude:
39
52
  - '**/*.md'
40
53
 
41
- Lint/UnreachableCode:
54
+ Style/CaseEquality:
42
55
  Exclude:
43
- - 'docs/result_objects/do_notation.md'
56
+ - 'lib/flows/contract/**/*'
57
+
58
+ Naming/RescuedExceptionsVariableName:
59
+ PreferredName: err
44
60
 
45
- Lint/Void:
61
+ Style/Documentation:
46
62
  Exclude:
47
- - 'docs/result_objects/do_notation.md'
63
+ - 'lib/flows/shared_context_pipeline/step.rb' # false positive here
64
+
65
+ Style/SlicingWithRange:
66
+ Enabled: false # to support Ruby 2.5
@@ -0,0 +1,27 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config --auto-gen-only-exclude`
3
+ # on 2020-08-22 07:49:05 UTC using RuboCop version 0.89.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 27
10
+ #
11
+ # *** NON-GENERATED COMMENT ***
12
+ # I'm not sure about enabling this cop
13
+ # * using super may has performance impact
14
+ # * but for some cases it still can be useful
15
+ # Research needed
16
+ Lint/MissingSuper:
17
+ Enabled: false
18
+
19
+ # Offense count: 5
20
+ # Configuration parameters: AllowSubject, Max.
21
+ #
22
+ # *** NON-GENERATED COMMENT ***
23
+ # Might be it's impossible to satisfy this cop or significant refactoring of these tests needed
24
+ RSpec/MultipleMemoizedHelpers:
25
+ Exclude:
26
+ - 'spec/flows/flow/node_spec.rb'
27
+ - 'spec/flows/util/inheritable_singleton_vars/isolation_strategy_spec.rb'
@@ -1 +1 @@
1
- 2.6.2
1
+ 2.6.6
@@ -0,0 +1 @@
1
+ --markup markdown
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ Types of changes:
9
+
10
+ * _Added_ - for new features.
11
+ * _Changed_ - for changes in existing functionality.
12
+ * _Deprecated_ - for soon-to-be removed features.
13
+ * _Removed_ - for now removed features.
14
+ * _Fixed_ -- for any bug fixes.
15
+ * _Security_ - in case of vulnerabilities.
16
+
17
+ ## [Unreleased]
18
+
19
+ ## [0.6.0] - 2020-09-23
20
+
21
+ ### Added
22
+
23
+ * `Flows::Plugin::Interface` basic implementation
24
+ * `Flows::SharedContextPipeline` simple sub-pipelines injection
25
+
26
+ ### Changed
27
+
28
+ * `Flows::SharedContextPipeline` callbacks execution method is changed to `instance_exec` (previously was `.call`)
29
+
30
+ ### Fixed
31
+
32
+ * `Flows::Plugin::DependencyInjector` and modules generated by `Flows::Util::InheritableSingletonVars::DupStrategy` now can be safely included several times in the inheritance chain
33
+
34
+ ## [0.5.1] - 2020-06-29
35
+
36
+ ### Fixed
37
+
38
+ * `Flows::SharedContextPipeline` wrap DSL failure in case of inheritance, [issue](https://github.com/ffloyd/flows/issues/18)
39
+
40
+ ## [0.5.0] - 2020-05-18
41
+
42
+ ### Added
43
+
44
+ * `Flows::SharedContextPipeline` wrap DSL, [issue](https://github.com/ffloyd/flows/issues/7)
45
+ * `Flows::Flow` routing integrity check on initialization
46
+ * `Flows::Plugin::OutputContract` skip contract DSL method
47
+ * `Flows::Plugin::Profiler` introduced. Report types: raw, tree and flat.
48
+
49
+ ### Changed
50
+
51
+ * `Flows::SharedContextPipeline` callback API, [issue](https://github.com/ffloyd/flows/issues/6)
52
+ * `Flows::Util` modules API, [issue](https://github.com/ffloyd/flows/issues/11)
53
+ * `Flows::Contract::CaseEq` default error expanded to present more context
54
+
55
+ ## [0.4.0] - 2020-04-21
56
+
57
+ ### Added
58
+
59
+ * `Flows::Contract` - type contracts with specific transformation feature.
60
+ * `Flows::Flow` - fast and lightweight logic execution engine, designed for
61
+ internal usage and library writers.
62
+ * `Flows::Plugin::DependencyInjector` - simple dependency injection plugin for your classes
63
+ * `Flows::Plugin::ImplicitInit` - allows to use `MyClass.call` instead of
64
+ `MyClass.new.call`, an class instance will be created once.
65
+ * `Flows::Plugin::OutputContract` - plugin for output type checks and
66
+ transformations for service objects which return `Flows::Result`.
67
+ * `Flows::Railway` - stupid simple implementation of the railway pattern.
68
+ * `Flows::Result` - general purpose Result Object designed after Rust Result type.
69
+ * `Flows::Result::Do` - do-notation for Result Objects.
70
+ * `Flows::SharedContextPipeline` - much more flexible adoption of the railway
71
+ pattern for Ruby.
72
+ * `Flows::Util::InheritableSingletonVars` - allows to define behavior for
73
+ singleton variables in the context of inheritance.
74
+ * `Flows::Util::PrependToClass` - allows to prepend some module to class even if
75
+ target module did not included directly into class.
76
+
77
+ [unreleased]: https://github.com/ffloyd/flows/compare/v0.6.0...HEAD
78
+ [0.6.0]: https://github.com/ffloyd/flows/compare/v0.5.1...v0.6.0
79
+ [0.5.1]: https://github.com/ffloyd/flows/compare/v0.5.0...v0.5.1
80
+ [0.5.0]: https://github.com/ffloyd/flows/compare/v0.4.0...v0.5.0
81
+ [0.4.0]: https://github.com/ffloyd/flows/compare/v0.3.0...v0.4.0
data/Gemfile CHANGED
@@ -2,9 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in flows.gemspec
4
4
  gemspec
5
-
6
- # temporary dev dependencies,
7
- # move it to gemspec after PRs will be merged into realesed version of a gem
8
- group :development do
9
- gem 'mdl', github: 'ffloyd/markdownlint', branch: 'update-kramdown-dep'
10
- end
data/README.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # Flows
2
2
 
3
- [![Build Status](https://github.com/ffloyd/flows/workflows/Build/badge.svg)](https://github.com/ffloyd/flows/actions)
3
+ [![Build Status](https://github.com/ffloyd/flows/workflows/Test/badge.svg)](https://github.com/ffloyd/flows/actions)
4
4
  [![codecov](https://codecov.io/gh/ffloyd/flows/branch/master/graph/badge.svg)](https://codecov.io/gh/ffloyd/flows)
5
5
  [![Gem Version](https://badge.fury.io/rb/flows.svg)](https://badge.fury.io/rb/flows)
6
6
 
7
7
  Small and fast ruby framework for implementing railway-like operations.
8
- By design it is close to [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/) and [Dry::Transaction](https://dry-rb.org/gems/dry-transaction/),
9
- but has simpler and flexible DSL for defining operations and matching results. Also `flows` is faster, see [Performance](#performance).
8
+ By design it is close to
9
+ [Trailblazer::Operation](http://trailblazer.to/gems/operation/2.0/),
10
+ [Dry::Transaction](https://dry-rb.org/gems/dry-transaction/) and Rust control
11
+ flow style.
12
+ Flows has simple and flexible DSL for defining operations and matching results.
13
+ Also `flows` is faster than Ruby's alternatives.
10
14
 
11
15
  `flows` has no production dependencies so it can be used with any framework.
12
16
 
@@ -15,7 +19,7 @@ but has simpler and flexible DSL for defining operations and matching results. A
15
19
  Add this line to your application's Gemfile:
16
20
 
17
21
  ```ruby
18
- gem 'flows'
22
+ gem 'flows', '~> 0.5'
19
23
  ```
20
24
 
21
25
  And then execute:
@@ -30,436 +34,236 @@ Or install it yourself as:
30
34
  gem install flows
31
35
  ```
32
36
 
33
- ## Usage
37
+ ## Supported Ruby versions
34
38
 
35
- ### `Flows::Flow`
39
+ CI tests against last patch versions every day:
36
40
 
37
- Low-level instrument for defining execution flows. Used internally as execution engine for `Flows::Operation`.
38
- Check out source code and specs for details.
41
+ * `MRI 2.5.x`
42
+ * `MRI 2.6.x`
39
43
 
40
- ### `Flows::Result`
44
+ `MRI 2.7.x` will be added later, right now (`2.7.1`) this version of MRI Ruby is too
45
+ unstable and produce segmentation faults inside RSpec internals.
41
46
 
42
- Result Object implementation. Inspired by [Dry::Monads::Result](https://dry-rb.org/gems/dry-monads/1.0/result/) and
43
- [Rust Result Objects](https://doc.rust-lang.org/1.30.0/book/2018-edition/ch09-02-recoverable-errors-with-result.html).
47
+ ## Usage & Documentation
44
48
 
45
- Main concepts & conventions:
49
+ * [YARD documentation](https://rubydoc.info/github/ffloyd/flows/master) - this
50
+ link is for master branch. You can also find YARD documentation for any released
51
+ version after `v0.4.0`. This documentation has a lot of examples, describes
52
+ motivation behind each abstraction, but lacks some guides and defined conventions.
53
+ * [Guides](https://ffloyd.github.io/flows/#/) - guides, conventions, integration
54
+ and migration notes. Will be done before `v1.0.0` release. Right now is under development.
46
55
 
47
- * separate classes for successful (`Flows::Result::Ok`) and failure (`Flows::Result::Err`) results
48
- * both classes has same parent class `Flows::Result`
49
- * result data should be a `Hash` with symbol keys and any values
50
- * result has a status
51
- * default status for successful results is `:success`
52
- * default status for failure results is `:failure`
56
+ ## Development
53
57
 
54
- Basic usage:
58
+ `Flows` is designed to be framework for your business logic. It is a big
59
+ responsibility. That's why `flows` has near to be sadistic development
60
+ conventions and linter setup.
55
61
 
56
- ```ruby
57
- # create successful result with data {a: 1, b: 2}
58
- result_ok = Flows::Result::Ok.new(a: 1, b: 2)
62
+ ### Anyone can make Flows even better
59
63
 
60
- # get `:a` from result
61
- result_ok.unwrap[:a] # 1
64
+ If you see some typos or unclear things in documentation or code - feel free to open
65
+ an issue. Even if you don't have plans to implement a solution - a problem reporting
66
+ will help development much. We cannot fix what we don't know.
62
67
 
63
- # get error data from result
64
- result_ok.error[:a] # raises exception
68
+ ### [Lefthook](https://github.com/Arkweid/lefthook) as a git hook manager
65
69
 
66
- # get status from result
67
- result_ok.status # :success
70
+ Installation on MacOS via Homebrew:
68
71
 
69
- # boolean flags
70
- result_ok.ok? # true
71
- result_ok.err? # false
72
+ ```sh
73
+ brew install Arkweid/lefthook/lefthook
74
+ ```
72
75
 
73
- # create successful result with data {a: 1, b: 2} and status `:custom`
74
- result_ok_custom = Flows::Result::Ok.new({ a: 1, b: 2 }, status: :custom)
76
+ Activation (in the root of the repo):
75
77
 
76
- # get status from result
77
- result_ok_custom.status # :custom
78
+ ```sh
79
+ lefthook install
80
+ ```
78
81
 
79
- # create failure result with data {a: 1, b: 2}
80
- result_err = Flows::Result::Err.new(a: 1, b: 2)
82
+ Run hooks manually:
81
83
 
82
- # get `:a` from result
83
- result_err.unwrap[:a] # raises exception
84
+ ```sh
85
+ lefthook run pre-commit
86
+ lefthook run pre-push
87
+ ```
84
88
 
85
- # get error data from result
86
- result_err.error[:a] # 1
89
+ Please, never turn off the pre-commit and pre-push hooks.
87
90
 
88
- # get status from result
89
- result_err.status # :failure
91
+ ### Rubocop linter
90
92
 
91
- # boolean flags
92
- result_ok.ok? # false
93
- result_ok.err? # true
93
+ [Rubocop](https://docs.rubocop.org/en/stable/) in this setup is responsible for:
94
94
 
95
- # create failure result with data {a: 1, b: 2} and status `:custom`
96
- result_err_custom = Flows::Result::Err.new({ a: 1, b: 2 }, status: :custom)
95
+ * defining code style (indentation, etc.)
96
+ * suggest performance improvements ([rubocop-performance](https://docs.rubocop.org/projects/performance/en/stable/))
97
+ * forces all that stuff (with some exceptions) to snippets in Markdown files ([rubocop-md](https://github.com/rubocop-hq/rubocop-md))
98
+ * forces unit-testing best practices ([rubocop-rspec](https://docs.rubocop.org/projects/rspec/en/latest/))
97
99
 
98
- # get status from result
99
- result_err_custom.status # :custom
100
- ```
100
+ Rubocop config for library and RSpec files should be close to standard one only
101
+ with minor amount of exceptions.
101
102
 
102
- Mixin `Flows::Result::Helpers` contains tools for simpler generating and matching Result Objects:
103
+ Code in Markdown snippets and `/bin` folder can ignore more rules. `/bin` folder
104
+ contains only development-related scripts and tools so it's ok to ease linter requirements.
103
105
 
104
- ```ruby
105
- include Flows::Result::Helpers
106
-
107
- # create successful result with data {a: 1, b: 2}
108
- result_ok = ok(a: 1, b: 2)
109
-
110
- # create successful result with data {a: 1, b: 2} and status `:custom`
111
- result_ok_custom = ok(:custom, a: 1, b: 2)
112
-
113
- # create failure result with data {a: 1, b: 2}
114
- result_err = err(a: 1, b: 2)
115
-
116
- # create failure result with data {a: 1, b: 2} and status `:custom`
117
- result_err_custom = err(:custom, a: 1, b: 2)
118
-
119
- # matching helpers
120
- result = SomeOperation.new.call
121
-
122
- case result
123
- when match_ok(:custom)
124
- # matches only successful results with status :custom
125
- do_something
126
- when match_ok
127
- # matches only successful results with any status
128
- do_something
129
- when match_err(:custom)
130
- # matches only failure results with status :custom
131
- do_something
132
- when match_err
133
- # matches only failure results with any status
134
- do_something
135
- end
136
- ```
106
+ Rubocop Metrics (ABC-size, method/class length, etc) must not be eased
107
+ globally. Never.
137
108
 
138
- ### `Flows::Operation`
109
+ ### Reek linter
139
110
 
140
- Let's solve simple task using operation:
111
+ [Ruby Reek](https://github.com/troessner/reek) is a very aggressive linter that
112
+ forces you to do a clean OOP design.
141
113
 
142
- * given numbers `a` and `b`
143
- * result should contain sum of this numbers
144
- * result should contain square of this sum
114
+ You will be tempted to just shut up this linter many times. But believe me, in 9
115
+ of 10 cases it worth to refactor. And after each such refactoring you will
116
+ understand OOP design better and better.
145
117
 
146
- ```ruby
147
- class Summator
148
- # Make this class an operation by including this module.
149
- # It adds DSL, initializer and call method.
150
- # Also it includes Flows::Result::Helper both on DSL and instance level.
151
- include Flows::Operation
152
-
153
- # This is step definitions.
154
- # In simplest form step defined by its name and
155
- # step implementation expected to be in a method
156
- # with same name.
157
- #
158
- # Steps will be executed in a definition order.
159
- step :validate
160
- step :calc_sum
161
- step :calc_square
162
-
163
- # Which keys of operation data we want to expose on success
164
- ok_shape :sum, :sum_square
165
-
166
- # Which keys of operation data we want to expose on failure
167
- err_shape :message
168
-
169
- # Step implementation receives execution context as keyword arguments.
170
- # For the first step context equals to operation arguments.
171
- #
172
- # Step implementation must return Result Object.
173
- # Result Objects's data will be merged into operation context.
174
- #
175
- # If result is successful - next step will be executed.
176
- # If not - operation terminates and returns failure.
177
- def validate(a:, b:, **)
178
- err(message: 'a is not a number') unless a.is_a?(Number)
179
- err(message: 'b is not a number') unless b.is_a?(Number)
180
-
181
- ok
182
- end
183
-
184
- def calc_sum(a:, b:, **)
185
- ok(sum: a + b)
186
- end
187
-
188
- # We may get data from previous steps because all results' data are merged to context.
189
- def calc_square(sum:, **)
190
- ok(sum_square: sum * sum)
191
- end
192
- end
193
-
194
- # prepare operation
195
- operation = Summator.new
196
-
197
- # execute operation
198
- result = operation.call(a: 1, b: 2)
199
-
200
- result.ok? # true
201
- result.unwrap # { sum: 3, sum_square: 9 } - only keys from success shape present
202
-
203
- result = operation.call(a: nil, b: nil)
204
-
205
- result.ok? # false
206
- result.error # { message: 'a is not a number' } - only keys from error shape present
207
- ```
118
+ ### Rest of the linters
208
119
 
209
- #### Result Shapes
120
+ * [MDL](https://github.com/markdownlint/markdownlint) - for consistent format of Markdown files
121
+ * [forspell](https://github.com/kkuprikov/forspell) - for spellchecking in comments and markdown files
122
+ * [inch](http://rrrene.org/inch/) - for documentation coverage suggestions (the
123
+ only optional linter)
210
124
 
211
- You may limit list of exposed fields by defining success and failure shapes. _After_ step definitions use `ok_shape` to define shapes of success result, and `err_shape` to define shapes of failure result. Examples:
125
+ ### Default Rake task and CI
212
126
 
213
- ```ruby
214
- # Set exposed keys for :success status of successful result.
215
- #
216
- # Success result will have shape like { key1: ..., key2: ... }
217
- #
218
- # If one of keys is missing in the final operation context an exception will be raised.
219
- ok_shape :key1, :key2
220
-
221
- # Set different exposed keys for different statuses.
222
- #
223
- # Operation result status is a status of last executed step result.
224
- ok_shape status1: %i[key1 key2],
225
- status2: [:key3]
226
-
227
- # Failure shapes defined in the same way:
228
- err_shape :key1, :key2
229
- err_shape status1: %i[key1 key2],
230
- status2: [:key3]
231
- ```
127
+ Default rake task (`bundle exec rake`) executes the following checks:
232
128
 
233
- Operation definition should have exact one `ok_shape` DSL-call and zero or one `err_shape` DSL-call. If you want to disable shaping
234
- you can write `no_shape` DSL-call instead of shape definitions.
129
+ * Rubocop
130
+ * Ruby Reek
131
+ * RSpec
132
+ * Spellcheck (forspell)
133
+ * MarkdownLint (mdl)
235
134
 
236
- #### Routing & Tracks
135
+ CI is also performing default Rake task. So, if you want to reproduce CI error
136
+ locally - just run `bundle exec rake`.
237
137
 
238
- You define side tracks, even nested ones:
138
+ Default Rake task is also executed as a pre-push git hook.
239
139
 
240
- ```ruby
241
- step :outer_1 # next step is outer_2
242
-
243
- track :some_track do
244
- step :inner_1 # next step is inner_2
245
- track :inner_track do
246
- step :deep_1 # next step is deep_2
247
- step :deep_2 # next step is inner_2
248
- end
249
- step :inner_2 # next step in outer_2
250
- end
251
-
252
- step :outer_2
253
- ```
140
+ ### Error reporting
254
141
 
255
- In definition above tracks will not be used because there is no routes to this tracks. You may define routing like this:
142
+ I hope no one will argue that clear errors makes development noticeably faster.
143
+ That's why _each_ exception in `flows` should be clear and easy to read.
256
144
 
257
- ```ruby
258
- # if result is successful and has status :to_some_track - next step will be inner_1
259
- # for any other successful results - outer_2
260
- step :outer_1, routes(
261
- when_ok(:to_some_track) => :some_track
262
- )
263
-
264
- track :some_track do
265
- step :inner * 1, routes(when_err => :inner_track) # redirect to inner_track on any failure result
266
- track :inner_track do
267
- step :deep_1, routes(when_ok(:some_status) => :outer_2) # you may redirect to steps too
268
- step :deep_2
269
- end
270
- step :inner_2
271
- end
272
-
273
- step :outer_2
274
- ```
145
+ This cannot be tested automatically: you only can test correctness
146
+ automatically, convenience can only be tested manually. That's why when you
147
+ introduce any new `raise` you have to:
275
148
 
276
- You also can use less verbose, but shorter form of definition:
149
+ * make an error message clear and descriptive
150
+ * add this error to _errors demo CLI_ (`bin/errors`)
151
+ * add this errors to _all the errors demo_ (`bin/all_the_errors`)
152
+ * make sure that error is displayed correctly and follows a style of the rest
153
+ of implemented errors
277
154
 
278
- ```ruby
279
- step :name,
280
- match_ok(:status) => :track_name,
281
- match_ok => :track_name
282
- ```
155
+ `bin/errors` is done using [GLI](https://davetron5000.github.io/gli/) library,
156
+ run `bin/errors -h` to explore possibilities.
283
157
 
284
- Step has default routes:
158
+ ### Performance
285
159
 
286
- ```ruby
287
- routes(
288
- when_ok => next_step_name,
289
- when_err => :term
290
- )
291
- ```
160
+ Ruby is slow. Moreover, Ruby is very slow. Yes, again. In the past time we had
161
+ to compare Ruby with Python. Python was faster and that's why people started to
162
+ complain about Ruby performance. That was fixed. But is Ruby fast nowadays? No.
163
+ Because languages like Clojure, Go, Rust, Elixir appeared and in comparison
164
+ with any of these languages Ruby is very very slow.
292
165
 
293
- Custom routes have bigger priority than default ones. Moreover, default routes can be overriden.
166
+ That's why you **must** be extra careful with performance. Some business
167
+ operations can be executed hundreds or even thousands times per request. Each
168
+ line of code in your abstraction will slow down such request a bit. That's why
169
+ you should think about each line performance.
294
170
 
295
- #### Lambda Steps
171
+ Also, it's nearly impossible to make zero-cost abstractions in Ruby. The best
172
+ thing you can do - to offload calculations to a class loading or initialization
173
+ step. Sacrifice some warm-up time to make runtime performance better.
296
174
 
297
- You can use lambda for in-place step implementation:
175
+ And to compare performance overhead between different `flows` abstractions
176
+ and another alternatives a benchmarking CLI was done: `bin/benchmark`.
298
177
 
299
- ```ruby
300
- step :name, ->(a:, b:, **) { ok(sum: a + b) }
301
- ```
178
+ This CLI is done using GLI, run `bin/benchmark -h` to explore possibilities.
302
179
 
303
- #### Dependency Injection
180
+ So far, `flows` offers the best performance among alternatives. And this CLI
181
+ is made to simplify comparison with alternatives and keep `flows` the fastest solution.
304
182
 
305
- You can override or inject step implementation on initialization:
183
+ ### Documentation
306
184
 
307
- ```ruby
308
- class Summator
309
- include Flows::Operation
185
+ Each public API method or module **must** be properly documented with examples
186
+ and motivation behind.
310
187
 
311
- step :sum
188
+ To run documentation server locally run `bin/docserver`.
312
189
 
313
- ok_shape :sum
314
- end
190
+ Respect `@since` YARD documentation tag. When some module, class or method has any
191
+ API change - you have to provide correct `@since` tag value to the documentation.
315
192
 
316
- summator = Summator.new(deps: {
317
- sum: ->(a:, b:, **) { ok(sum: a + b) }
318
- })
193
+ ### Documentation Driven Development
319
194
 
320
- summator.call(a: 1, b: 2).unwrap[:sum] # 3
321
- ```
195
+ When you about to do some work, the following guideline can lead to the best
196
+ results:
322
197
 
323
- #### Wrapping steps
198
+ * first, write needed class and method structure without implementation
199
+ * write YARD documentation with motivation and usage examples for each public
200
+ class, method, module.
201
+ * write unit tests, check that tests are failing
202
+ * write implementation until tests are green
324
203
 
325
- You can wrap several steps with some logic:
204
+ Yes, it's TDD approach with documentation step prepended.
326
205
 
327
- ```ruby
328
- step :first
329
-
330
- wrap :wrapper do
331
- step :wrapped
332
- end
333
-
334
- def wrapper(**_context)
335
- # do smth
336
- result = yield # execute wrapped steps
337
- # do smth or modify result
338
- result
339
- end
340
- ```
206
+ ### Unit test
341
207
 
342
- There is routing limitation when you use wrap:
208
+ Each public API method or module **must** be properly tested. Internal modules
209
+ can be tested indirectly through public API.
343
210
 
344
- * outside `wrap` block you may route to wrapped block by wrapper name (`:wrapper` in the provided example)
345
- * you may route wrapped steps only to wrapped steps in the same wrap block
346
- * you cannot route to wrapped steps from outside
211
+ Test coverage **must** be higher than 95%.
347
212
 
348
- ## Performance
213
+ ### Commit naming
349
214
 
350
- You can compare performance for some cases by executing `bin/benchmark`. Examples for benchmark are presented in `bin/examples.rb`.
215
+ You **must** follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
351
216
 
352
- `Flows::Operation` and `Dry::Trancation` may be executed in two ways:
217
+ Allowed prefixes since `v0.4.0`:
353
218
 
354
- _Build once:_ when we create operation instance once (build operation):
219
+ * `feat:` - for new features
220
+ * `fix:` - for bugfixes
221
+ * `perf:` - for performance improvements
222
+ * `refactor:` - for refactoring work
223
+ * `ci:` - updates for CI configuration
224
+ * `docs:` - for documentation update
355
225
 
356
- ```ruby
357
- operation = OperationClass.new
226
+ Sometimes commit can have several responsibilities. As example: when you write
227
+ documentation, test and implementation for a feature in the one commit. You can do
228
+ extra effort to split and rearrange commits to make it atomic. But does it
229
+ really provide significant value if we already have a strong convention for
230
+ changelog (see the next section)?
358
231
 
359
- 10_000.times { operation.call }
360
- ```
232
+ So, when you in such situation use the first applicable prefix in the list:
233
+ between `docs` and `refactor` - pick `refactor`.
361
234
 
362
- _Build each time:_ when we create operation instance each execution:
235
+ Also, there is one more special prefix for release commits. Release commit
236
+ messages **must** look like: `release: v0.4.0`.
363
237
 
364
- ```ruby
365
- 10_000.times { OperationClass.new.call }
366
- ```
238
+ ### Changelog
367
239
 
368
- `flows` and `dry` are much faster in _build once_ way of using. Note that Trailblazer gives you only one way to execute operation.
369
-
370
- ### Benchmark Results
371
-
372
- Host:
373
-
374
- * MacBook Pro (13-inch, 2017, Four Thunderbolt 3 Ports)
375
- * 3.1 GHz Intel Core i5
376
- * 8 GB 2133 MHz LPDDR3
377
-
378
- Results:
379
-
380
- ```text
381
- --------------------------------------------------
382
- - task: A + B, one step implementation
383
- --------------------------------------------------
384
- Warming up --------------------------------------
385
- Flows::Operation (build each time)
386
- 9.147k i/100ms
387
- Flows::Operation (build once)
388
- 25.738k i/100ms
389
- Dry::Transaction (build each time)
390
- 2.294k i/100ms
391
- Dry::Transaction (build once)
392
- 21.836k i/100ms
393
- Trailblazer::Operation
394
- 5.057k i/100ms
395
- Calculating -------------------------------------
396
- Flows::Operation (build each time)
397
- 96.095k (± 2.3%) i/s - 484.791k in 5.047684s
398
- Flows::Operation (build once)
399
- 281.248k (± 1.7%) i/s - 1.416M in 5.034728s
400
- Dry::Transaction (build each time)
401
- 23.683k (± 1.7%) i/s - 119.288k in 5.038506s
402
- Dry::Transaction (build once)
403
- 237.379k (± 3.3%) i/s - 1.201M in 5.066073s
404
- Trailblazer::Operation
405
- 52.676k (± 1.5%) i/s - 268.021k in 5.089306s
406
-
407
- Comparison:
408
- Flows::Operation (build once): 281248.4 i/s
409
- Dry::Transaction (build once): 237378.7 i/s - 1.18x slower
410
- Flows::Operation (build each time): 96094.9 i/s - 2.93x slower
411
- Trailblazer::Operation: 52676.3 i/s - 5.34x slower
412
- Dry::Transaction (build each time): 23682.9 i/s - 11.88x slower
413
-
414
-
415
- --------------------------------------------------
416
- - task: ten steps returns successful result
417
- --------------------------------------------------
418
- Warming up --------------------------------------
419
- Flows::Operation (build each time)
420
- 1.496k i/100ms
421
- Flows::Operation (build once)
422
- 3.847k i/100ms
423
- Dry::Transaction (build each time)
424
- 274.000 i/100ms
425
- Dry::Transaction (build once)
426
- 2.992k i/100ms
427
- Trailblazer::Operation
428
- 1.082k i/100ms
429
- Calculating -------------------------------------
430
- Flows::Operation (build each time)
431
- 15.013k (± 3.8%) i/s - 76.296k in 5.089734s
432
- Flows::Operation (build once)
433
- 39.239k (± 1.6%) i/s - 196.197k in 5.001538s
434
- Dry::Transaction (build each time)
435
- 2.743k (± 3.7%) i/s - 13.700k in 5.002847s
436
- Dry::Transaction (build once)
437
- 30.441k (± 1.8%) i/s - 152.592k in 5.014565s
438
- Trailblazer::Operation
439
- 11.022k (± 1.4%) i/s - 55.182k in 5.007543s
440
-
441
- Comparison:
442
- Flows::Operation (build once): 39238.6 i/s
443
- Dry::Transaction (build once): 30440.5 i/s - 1.29x slower
444
- Flows::Operation (build each time): 15012.7 i/s - 2.61x slower
445
- Trailblazer::Operation: 11022.1 i/s - 3.56x slower
446
- Dry::Transaction (build each time): 2743.0 i/s - 14.30x slower
447
- ```
240
+ Starting from `v0.4.0` [keep a changelog](https://keepachangelog.com/en/1.0.0/)
241
+ guideline must be met.
448
242
 
449
- ## Development
243
+ If you adding something - provide some lines to the unreleased section of the `CHANGELOG.md`.
244
+
245
+ ### Versioning
450
246
 
451
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
247
+ The project strictly follows [SemVer](https://semver.org/spec/v2.0.0.html).
452
248
 
453
- To install this gem onto your local machine, run `bundle exec rake install`.
249
+ After `v1.0.0` even smallest backward incompatible change will bump major
250
+ version. _No exceptions._
454
251
 
455
- ## Contributing
252
+ Commit with a version bump should contain _only_ version bump and CHANGELOG.md update.
456
253
 
457
- Bug reports and pull requests are welcome on GitHub at [ffloyd/fflows](https://github.com/ffloyd/flows). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
254
+ ### GitHub Flow
458
255
 
459
- ## License
256
+ Since `v0.4.0` this repo strictly follow [GitHub
257
+ Flow](https://guides.github.com/introduction/flow/) with some additions:
460
258
 
461
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
259
+ * branch naming using dash: `improved-contexts`
260
+ * use [references to
261
+ issues](https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword)
262
+ in commit messages and make links to issues in CHANGELOG.md
462
263
 
463
- ## Code of Conduct
264
+ ### Planned features for v1.0.0
464
265
 
465
- Everyone interacting in the Flows project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/ffloyd/flows/blob/master/CODE_OF_CONDUCT.md).
266
+ * validation framework
267
+ * error reporting improvements
268
+ * various plugins for SCP (tracing, benchmarking, logging, etc)
269
+ * site with guides and conventions