enm 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.
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ENM
4
+ # A namespace allows grouping multiple databases together for lookup,
5
+ # to be exposed in an atomic constant through {ENM.current}.
6
+ #
7
+ # It is not meant to be instantiated directly, instead, use {ENM.configure_current}
8
+ # to set it up for the gem. More advanced use cases can use {.build} for their own
9
+ # use cases, but the interface is not guaranteed to be stable.
10
+ class Namespace
11
+ include Dry::Initializer[undefined: false].define -> do
12
+ option :databases, ENM::Types::DatabaseMap
13
+ option :default, ENM::Types::DefaultDatabase, default: proc { databases.first[1] if databases.one? }
14
+ end
15
+
16
+ def initialize(...)
17
+ super
18
+
19
+ freeze
20
+ end
21
+
22
+ # @param [String, Symbol] name
23
+ # @return [ENM::Database, nil]
24
+ def [](name) = @databases[name.to_s]
25
+
26
+ # @param [String, Symbol] name
27
+ # @param [String, Symbol, nil] schema
28
+ # @param [String, Symbol, nil] on the name of the database to get the enum from.
29
+ # If `nil`, the default database will be used.
30
+ # @param [String, Symbol, #to_s, #to_sym, nil] default whether to use a
31
+ # certain value as dry-type's default, since it must be defined _before_
32
+ # the enum call.
33
+ # @param [:string, :symbol] mode the underlying type of the enum values.
34
+ # @return [Dry::Types::Type]
35
+ def dry(name, schema: nil, on: nil, default: nil, mode: :string)
36
+ enum = get(name, schema:, on:)
37
+
38
+ enum.dry(default:, mode:)
39
+ end
40
+
41
+ def has_default? = !@default.nil?
42
+
43
+ # @see ENM::Database#get
44
+ # @param [String, Symbol] name
45
+ # @param [String, Symbol, nil] schema
46
+ # @param [String, Symbol, nil] on the name of the database to get the enum from.
47
+ # If `nil`, the default database will be used.
48
+ # @return [ENM::Enum]
49
+ def get(name, schema: nil, on: nil)
50
+ database = database_for(on)
51
+
52
+ database.get(name, schema:)
53
+ end
54
+
55
+ private
56
+
57
+ # @param [#to_s, nil] name
58
+ # @raise [ENM::NoDefaultDatabaseError] if `name` is `nil` and no default database is configured
59
+ # @raise [ENM::DatabaseNotFoundError] if `name` is not
60
+ # @return [ENM::Database]
61
+ def database_for(name)
62
+ if name.nil?
63
+ if has_default?
64
+ @default
65
+ else
66
+ raise ENM::NoDefaultDatabaseError, "No default database configured for namespace"
67
+ end
68
+ else
69
+ @databases.fetch(name.to_s) do
70
+ raise ENM::DatabaseNotFoundError, "Database #{name} not found in namespace"
71
+ end
72
+ end
73
+ end
74
+
75
+ class << self
76
+ private_class_method :new
77
+
78
+ # @yieldparam [ENM::Namespace::Builder] builder
79
+ # @yieldreturn [void]
80
+ # @return [ENM::Namespace]
81
+ def build
82
+ builder = Builder.new
83
+
84
+ yield(builder)
85
+
86
+ new(**builder.to_h)
87
+ end
88
+ end
89
+
90
+ # A builder for {ENM::Namespace}.
91
+ # @see ENM::Namespace.build
92
+ # @api private
93
+ class Builder
94
+ def initialize
95
+ @databases = {}
96
+ @default = nil
97
+ end
98
+
99
+ # Add a {ENM::Database database} to the namespace from a RON string.
100
+ #
101
+ # @api public
102
+ # @see #from_ron_file!
103
+ # @param [String] ron
104
+ # @param [Integer] version
105
+ # @param [Boolean] default
106
+ # @return [void]
107
+ def from_ron!(ron, version: ENM::RON_VERSION, default: false)
108
+ add!(Database.from_ron(version, ron), default:)
109
+ end
110
+
111
+ # Add a {ENM::Database database} to the namespace from a RON file.
112
+ #
113
+ # @api public
114
+ # @see #from_ron!
115
+ # @param [String] path
116
+ # @param [Integer] version
117
+ # @param [Boolean] default
118
+ # @return [void]
119
+ def from_ron_file!(path, version: ENM::RON_VERSION, default: false)
120
+ add!(Database.from_ron_file(version, path), default:)
121
+ end
122
+
123
+ # Add a {ENM::Database database} to the namespace from a SQL string.
124
+ #
125
+ # @api public
126
+ # @see #from_sql_file!
127
+ # @param [String, Symbol] name
128
+ # @param [String] sql
129
+ # @param [Boolean] default
130
+ # @return [void]
131
+ def from_sql!(name, sql, default: false)
132
+ add!(Database.from_sql(name, sql), default:)
133
+ end
134
+
135
+ # Add a {ENM::Database database} to the namespace from a SQL string.
136
+ #
137
+ # @api public
138
+ # @see #from_sql!
139
+ # @param [String, Symbol] name
140
+ # @param [String] path
141
+ # @param [Boolean] default
142
+ # @return [void]
143
+ def from_sql_file!(name, path, default: false)
144
+ add!(Database.from_sql_file(name, path), default:)
145
+ end
146
+
147
+ # Set the default database for the namespace.
148
+ #
149
+ # @api public
150
+ # @param [String, Symbol] name
151
+ # @raise [ENM::DatabaseNotFoundError] if no database with the given name is found in the namespace
152
+ # @return [void]
153
+ def default!(name)
154
+ @default = @databases.fetch(name.to_s) do
155
+ raise ENM::DatabaseNotFoundError, "Default database #{name} not found in namespace"
156
+ end
157
+ end
158
+
159
+ # @api private
160
+ # @return [{ Symbol => Object }]
161
+ def to_h
162
+ { databases: @databases, default: @default }
163
+ end
164
+
165
+ private
166
+
167
+ # @param [ENM::Database] database
168
+ # @param [Boolean] default
169
+ # @return [void]
170
+ def add!(database, default: false)
171
+ @databases[database.name.to_s] = database
172
+ @default = database if default
173
+ end
174
+ end
175
+ end
176
+ end
data/lib/enm/types.rb ADDED
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ENM
4
+ # @api private
5
+ module Types
6
+ # @!parse [ruby]
7
+ # include Dry::Types
8
+ __send__(:include, Dry.Types)
9
+
10
+ # @return [Dry::Types::Type(ENM::Database)]
11
+ Database = Instance(ENM::Database)
12
+
13
+ # @return [Dry::Types::Type(Hash{String => ENM::Database})]
14
+ DatabaseMap = Hash.map(String, Database)
15
+
16
+ # @return [Dry::Types::Type(ENM::Database, nil)]
17
+ DefaultDatabase = Database.optional
18
+
19
+ # @return [Dry::Types::Type(#to_s)]
20
+ DefaultValue = Coercible::String.constrained(filled: true)
21
+
22
+ # @return [Dry::Types::Type(ENM::Enum)]
23
+ Enum = Instance(ENM::Enum)
24
+
25
+ # @return [Dry::Types::Type(:string, :symbol)]
26
+ DryMode = Coercible::Symbol.default(:string).enum(:string, :symbol)
27
+ end
28
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ENM
4
+ VERSION = "0.1.0"
5
+ end
data/lib/enm.rb ADDED
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/initializer"
4
+ require "dry/types"
5
+ require "pathname"
6
+
7
+ require_relative "enm/version"
8
+ require "enm/enm"
9
+ require "enm/types"
10
+ require "enm/database"
11
+ require "enm/enum"
12
+ require "enm/namespace"
13
+
14
+ # A parser and introspecter for PostgreSQL enums, made to be used
15
+ # within Rails or other Ruby applications.
16
+ module ENM
17
+ # @abstract The base error class for `ENM`.
18
+ class Error < StandardError; end
19
+
20
+ class AlreadyConfiguredError < Error; end
21
+
22
+ class DatabaseNotFoundError < Error; end
23
+
24
+ class DuplicatedTypeError < Error; end
25
+
26
+ class DuplicatedValueError < Error; end
27
+
28
+ class EnumConflictError < Error; end
29
+
30
+ class EnumNotFoundError < Error; end
31
+
32
+ # An error that is raised when trying to specify a default value that
33
+ # is not present in the enum.
34
+ class InvalidDefaultError < Error; end
35
+
36
+ # An error that is raised when there is some sort of IO error during
37
+ # file reading / writing operations.
38
+ class IOError < Error; end
39
+
40
+ # An error that is raised when trying to access the current namespace when
41
+ # it has yet to be initialized.
42
+ class NoCurrentNamespaceError < Error; end
43
+
44
+ # An error that is raised when trying to look up an enum without
45
+ # specifying the database, and no default database has been set.
46
+ class NoDefaultDatabaseError < Error; end
47
+
48
+ # An error that is raised when there is an issue during parsing of SQL input.
49
+ class ParseError < Error; end
50
+
51
+ # An error that is raised when there is an issue with RON serialization or deserialization.
52
+ class SerializationError < Error; end
53
+
54
+ # An error that is raised when specifying an unknown version during RON deserialization.
55
+ class UnknownVersionError < Error; end
56
+
57
+ class << self
58
+ # Configure {.current} namespace for the gem.
59
+ #
60
+ # @param [Boolean] force
61
+ # @raise [ENM::AlreadyConfiguredError] if a current namespace is already configured and `force` is not set to `true`
62
+ # @yieldparam [ENM::Namespace::Builder] builder
63
+ # @yieldreturn [void]
64
+ # @return [void]
65
+ def configure_current(force: false, &block)
66
+ namespace = Namespace.build(&block)
67
+
68
+ raise AlreadyConfiguredError, "Current namespace is already configured" if current? && !force
69
+
70
+ Thread.current.thread_variable_set(:enm_current_namespace, namespace)
71
+ end
72
+
73
+ # @!attribute [r] current
74
+ # @raise [ENM::NoCurrentNamespaceError] if no current namespace is set
75
+ # @return [ENM::Namespace]
76
+ def current
77
+ Thread.current.thread_variable_get(:enm_current_namespace) || raise(ENM::NoCurrentNamespaceError, "No current namespace set")
78
+ end
79
+
80
+ # Whether a current namespace is set.
81
+ def current? = Thread.current.thread_variable?(:enm_current_namespace)
82
+
83
+ # @see ENM::Namespace#[]
84
+ # @param [String, Symbol] name
85
+ # @return [ENM::Database, nil]
86
+ def [](name) = current[name]
87
+
88
+ # @see ENM::Namespace#dry
89
+ # @return [Dry::Types::Type]
90
+ def dry(...) = current.dry(...)
91
+
92
+ # @see ENM::Namespace#get
93
+ # @return [ENM::Enum]
94
+ def get(...) = current.get(...)
95
+
96
+ # Reset the current namespace, caches, etc.
97
+ #
98
+ # @api private
99
+ # @return [void]
100
+ def reset!
101
+ Thread.current.thread_variable_set(:enm_current_namespace, nil)
102
+
103
+ ENM::Enum.cache.clear
104
+ end
105
+ end
106
+ end
data/mise.toml ADDED
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ ruby = "4.0.1"
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: enm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexa Grey
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: dry-initializer
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: dry-types
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '1.8'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '1.8'
54
+ description: Parse and serialize PostgreSQL enum sets in Ruby, powered by Rust.
55
+ email:
56
+ - devel@mouse.vc
57
+ executables: []
58
+ extensions:
59
+ - ext/enm/extconf.rb
60
+ extra_rdoc_files: []
61
+ files:
62
+ - CHANGELOG.md
63
+ - CODE_OF_CONDUCT.md
64
+ - Cargo.lock
65
+ - Cargo.toml
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - ext/enm/Cargo.toml
70
+ - ext/enm/build.rs
71
+ - ext/enm/extconf.rb
72
+ - ext/enm/src/lib.rs
73
+ - lib/enm.rb
74
+ - lib/enm/database.rb
75
+ - lib/enm/enum.rb
76
+ - lib/enm/namespace.rb
77
+ - lib/enm/types.rb
78
+ - lib/enm/version.rb
79
+ - mise.toml
80
+ homepage: https://github.com/scryptmouse/enm
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ allowed_push_host: https://rubygems.org
85
+ homepage_uri: https://github.com/scryptmouse/enm
86
+ source_code_uri: https://github.com/scryptmouse/enm
87
+ changelog_uri: https://github.com/scryptmouse/enm/blob/main/CHANGELOG.md
88
+ rubygems_mfa_required: 'true'
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: 3.4.0
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 4.0.8
104
+ specification_version: 4
105
+ summary: Parse and serialize PostgreSQL enum sets in Ruby, powered by Rust.
106
+ test_files: []