concurrent_pipeline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "yaml"
5
+
6
+ module ConcurrentPipeline
7
+ module Stores
8
+ module Yaml
9
+ class Db
10
+ Reader = Struct.new(:store) do
11
+ def find(...)
12
+ store.find(...)
13
+ end
14
+
15
+ def all(...)
16
+ store.all(...)
17
+ end
18
+
19
+ def everything(...)
20
+ store.everything(...)
21
+ end
22
+
23
+ def to_h(...)
24
+ store.to_h(...)
25
+ end
26
+
27
+ def changeset
28
+ store.changeset
29
+ end
30
+
31
+ def reader?
32
+ true
33
+ end
34
+ end
35
+
36
+ attr_reader :data, :registry, :after_apply
37
+ def initialize(data:, registry:, after_apply: nil)
38
+ @data = data
39
+ @registry = registry
40
+ @after_apply = after_apply
41
+ end
42
+
43
+ def changeset
44
+ Changeset.new(registry: registry)
45
+ end
46
+
47
+ def apply(chgs)
48
+ changesets = chgs.is_a?(Array) ? chgs : [chgs]
49
+ results = changesets.flat_map { _1.apply(self) }
50
+ diffed = results.any?(&:diff?)
51
+ after_apply&.call(changesets) if diffed
52
+ diffed
53
+ end
54
+
55
+ def reader?
56
+ false
57
+ end
58
+
59
+ def reader
60
+ Reader.new(self)
61
+ end
62
+
63
+ def to_h
64
+ data
65
+ end
66
+
67
+ def everything
68
+ data.each_with_object({}) do |(type, subdata), all_of_it|
69
+ all_of_it[type] = subdata.map { |attrs| registry.build(type, attrs) }
70
+ end
71
+ end
72
+
73
+ def all(type)
74
+ type = registry.type_for(type)
75
+
76
+ data
77
+ .fetch(type, [])
78
+ .map { registry.build(type, _1) }
79
+ end
80
+
81
+ def find(type, id)
82
+ type = registry.type_for(type)
83
+ attrs = (data[type] || []).find { |attrs| attrs[:id] == id }
84
+ registry.build(type, attrs)
85
+ end
86
+
87
+ def set(data)
88
+ @data = data
89
+ end
90
+
91
+ def create(type:, attributes:)
92
+ type = registry.type_for(type)
93
+
94
+ data[type] ||= []
95
+ data[type] << attributes
96
+ end
97
+
98
+ def update(id:, type:, attributes:)
99
+ type = registry.type_for(type)
100
+ attrs = (data[type] || []).find { |attrs| attrs[:id] == id }
101
+ raise "Not found" unless attrs
102
+
103
+ was_updated = attributes.any? { |k, v| attrs[k] != v }
104
+ attrs.merge!(attributes)
105
+ was_updated
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "yaml"
5
+
6
+ require_relative "db"
7
+
8
+ module ConcurrentPipeline
9
+ module Stores
10
+ module Yaml
11
+ class History
12
+ class Version
13
+ attr_reader :index, :registry, :changesets
14
+ def initialize(index:, changesets:, registry:)
15
+ @index = index
16
+ @changesets = changesets[0..index]
17
+ @registry = registry
18
+ end
19
+
20
+ def store
21
+ @store ||= (
22
+ Db
23
+ .new(data: {}, registry: registry)
24
+ .tap { _1.apply(changesets) }
25
+ .reader
26
+ )
27
+ end
28
+
29
+ def diff
30
+ changesets.last.as_json
31
+ end
32
+ end
33
+
34
+ attr_reader :path, :registry
35
+ def initialize(registry:, path:)
36
+ @registry = registry
37
+ @path = path
38
+ end
39
+
40
+ def versions
41
+ @versions ||= (
42
+ changesets
43
+ .count
44
+ .times
45
+ .map { |i|
46
+ Version.new(
47
+ index: i,
48
+ changesets: changesets,
49
+ registry: registry
50
+ )
51
+ }
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def changesets
58
+ @changesets ||= (
59
+ YAML
60
+ .load_stream(File.read(path))
61
+ .map { |v| Changeset.from_json(json: v, registry: registry) }
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+ require "yaml"
5
+
6
+ require_relative "yaml/db"
7
+ require_relative "yaml/history"
8
+
9
+ module ConcurrentPipeline
10
+ module Stores
11
+ module Yaml
12
+ def self.build_writer(data:, dir:, registry:)
13
+ data_path = File.join(dir, "data.yml")
14
+ versions_path = File.join(dir, "versions.yml")
15
+
16
+ after_apply = ->(changesets) do
17
+ File.write(data_path, data.to_yaml)
18
+ File.open(versions_path, "a") do |f|
19
+ changesets
20
+ .map { _1.as_json.to_yaml }
21
+ .join("\n")
22
+ .then { f.puts(_1)}
23
+ end
24
+ end
25
+
26
+ db = Db.new(data: data, registry: registry, after_apply: after_apply)
27
+
28
+ changeset = db.changeset
29
+ changeset.deltas << Changeset::InitialDelta.new(data: data)
30
+ db.apply(changeset)
31
+ db
32
+ end
33
+
34
+ def self.history(dir:, registry:)
35
+ path = File.join(dir, "versions.yml")
36
+ History.new(registry: registry, path: path)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConcurrentPipeline
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "concurrent_pipeline/version"
4
+ require_relative "concurrent_pipeline/model"
5
+ require_relative "concurrent_pipeline/pipeline"
6
+ require_relative "concurrent_pipeline/producer"
7
+ require_relative "concurrent_pipeline/shell"
8
+
9
+ require "logger"
10
+
11
+ module ConcurrentPipeline
12
+ class Error < StandardError; end
13
+ # Your code goes here...
14
+ Log = Logger.new($stdout).tap { _1.level = Logger::WARN }
15
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: concurrent_pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Pete Kinnecom
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2024-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby-edge
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ description:
28
+ email:
29
+ - git@k7u7.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rubocop.yml"
35
+ - ".ruby-version"
36
+ - README.md
37
+ - Rakefile
38
+ - concurrency.md
39
+ - concurrent_pipeline.gemspec
40
+ - lib/concurrent_pipeline.rb
41
+ - lib/concurrent_pipeline/changeset.rb
42
+ - lib/concurrent_pipeline/model.rb
43
+ - lib/concurrent_pipeline/pipeline.rb
44
+ - lib/concurrent_pipeline/processors/actor_processor.rb
45
+ - lib/concurrent_pipeline/producer.rb
46
+ - lib/concurrent_pipeline/read_only_store.rb
47
+ - lib/concurrent_pipeline/registry.rb
48
+ - lib/concurrent_pipeline/shell.rb
49
+ - lib/concurrent_pipeline/store.rb
50
+ - lib/concurrent_pipeline/stores/versioned.rb
51
+ - lib/concurrent_pipeline/stores/yaml.rb
52
+ - lib/concurrent_pipeline/stores/yaml/db.rb
53
+ - lib/concurrent_pipeline/stores/yaml/history.rb
54
+ - lib/concurrent_pipeline/version.rb
55
+ homepage: https://github.com/petekinnecom/concurrent_pipeline
56
+ licenses:
57
+ - WTFPL
58
+ metadata:
59
+ allowed_push_host: https://rubygems.org
60
+ homepage_uri: https://github.com/petekinnecom/concurrent_pipeline
61
+ source_code_uri: https://github.com/petekinnecom/concurrent_pipeline
62
+ changelog_uri: https://github.com/petekinnecom/concurrent_pipeline
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: 3.0.0
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubygems_version: 3.4.19
79
+ signing_key:
80
+ specification_version: 4
81
+ summary: Define a pipeline of tasks, run them concurrently, and see a versioned history
82
+ of all changes along the way.
83
+ test_files: []