briefly 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.
data/lib/briefly.rb ADDED
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "candor"
4
+
5
+ require "briefly/version"
6
+ require "briefly/errors"
7
+ require "briefly/definition"
8
+ require "briefly/error_registry"
9
+ require "briefly/facade"
10
+ require "briefly/builder"
11
+
12
+ # A terse, curated facade over an application's most frequently reached-for objects.
13
+ #
14
+ # App = Briefly.define do
15
+ # use Briefly::Rails
16
+ # shortcut(:redis) { REDIS_POOL }
17
+ # end
18
+ module Briefly
19
+ autoload :Rails, "briefly/rails"
20
+
21
+ @errors = ErrorRegistry.new
22
+
23
+ # The packs this gem ships, under the short names +use+ accepts. Values are constant paths, never
24
+ # constants: naming +Briefly::Rails::DB+ here would resolve it at load and defeat the autoload above.
25
+ @packs = {
26
+ "rails" => "Briefly::Rails",
27
+ "rails/config" => "Briefly::Rails::Config",
28
+ "rails/env" => "Briefly::Rails::Env",
29
+ "rails/view" => "Briefly::Rails::View",
30
+ "rails/db" => "Briefly::Rails::DB",
31
+ "rails/reload" => "Briefly::Rails::Reload"
32
+ }
33
+
34
+ class << self
35
+ # Builds a facade. The block is +instance_eval+'d on a {Briefly::Builder}.
36
+ #
37
+ # @yield [] the shortcut declarations
38
+ # @return [Briefly::Facade]
39
+ def define(&) = Facade.new.configure(&)
40
+
41
+ # Registers a pack under a short name, so +use "myapp/redis"+ resolves it. Re-registering a name
42
+ # overrides it. There is no inflection and no path guessing: this table is the only source of truth.
43
+ #
44
+ # @param name [String, Symbol] the short name
45
+ # @param pack [#install, String] the pack, or a constant path resolved on first use
46
+ # @return [self]
47
+ def register(name, pack)
48
+ @packs[name.to_s] = pack
49
+ self
50
+ end
51
+
52
+ # Resolves a short name to a pack. A registered constant path is resolved here, not at registration,
53
+ # so a pack's file loads only when something uses it.
54
+ #
55
+ # The path is walked one segment at a time rather than handed to +Object.const_get+ whole, so a
56
+ # +NameError+ raised *inside* a pack's file as it autoloads propagates untouched. Rescuing around
57
+ # the whole resolution would launder that bug into an {Briefly::UnknownPackError}.
58
+ #
59
+ # @param name [String, Symbol]
60
+ # @return [#install]
61
+ # @raise [Briefly::UnknownPackError] if +name+ is not registered, or names a path that does not resolve
62
+ def pack(name)
63
+ entry = @packs.fetch(name.to_s) { raise UnknownPackError, "unknown pack: #{name.inspect}" }
64
+ return entry unless entry.is_a?(String)
65
+
66
+ entry.split("::").reduce(Object) do |mod, segment|
67
+ unless mod.const_defined?(segment, false)
68
+ raise UnknownPackError, "pack #{name.inspect} names #{entry.inspect}, which does not resolve"
69
+ end
70
+
71
+ mod.const_get(segment, false)
72
+ end
73
+ end
74
+
75
+ # Registers a handler that applies to every shortcut on every facade, consulted only after the
76
+ # facade's own handlers. Takes no shortcut names.
77
+ #
78
+ # @param error_class [Class] matched against the raised error and its subclasses
79
+ # @yield [error, shortcut_name] the recovery block; its return value becomes the shortcut's value
80
+ # @return [self]
81
+ def rescue_from(error_class, &handler)
82
+ unless error_class.is_a?(Class)
83
+ raise ArgumentError, "rescue_from expects the error class first, got #{error_class.inspect}"
84
+ end
85
+ raise ArgumentError, "rescue_from(#{error_class}) requires a block" unless handler
86
+
87
+ @errors.add(error_class, nil, handler)
88
+ self
89
+ end
90
+
91
+ # @return [Briefly::ErrorRegistry] the global registry
92
+ attr_reader :errors
93
+ end
94
+ end
@@ -0,0 +1,28 @@
1
+ module Briefly
2
+ # Receives the DSL. Blocks are `instance_eval`'d here, never on the facade.
3
+ class Builder
4
+ @facade: Facade
5
+ @defs: Hash[Symbol, Definition]
6
+ @children: Hash[Symbol, Facade]
7
+ @errors: Array[Briefly::error_entry]
8
+
9
+ # One collected namespace pass per name, held until `compile!` has validated the whole tree.
10
+ @pending: Hash[Symbol, Builder]
11
+
12
+ attr_reader facade: Facade
13
+
14
+ def initialize: (Facade facade, Hash[Symbol, Definition] defs, Hash[Symbol, Facade] children) -> void
15
+ def use: (pack_ref pack, **untyped opts) -> self
16
+ def namespace: (Symbol name) { (?) [self: Builder] -> void } -> Symbol
17
+ def shortcut: (Symbol canonical, *Symbol aliases) { (?) [self: Facade] -> untyped } -> Symbol
18
+ def memoize: (Symbol name, **untyped opts) -> Symbol
19
+ def rescue_from: (Class error_class, *(Symbol | Array[Symbol]) names) { (StandardError, Symbol) -> untyped } -> self
20
+ def compile!: () -> Briefly::plan
21
+
22
+ private
23
+
24
+ def validate_name!: (untyped name) -> void
25
+ def purge: (Array[Symbol] names, except: Symbol) -> void
26
+ def fetch: (Symbol name) -> Definition
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Briefly
2
+ # One shortcut declaration: its canonical name, its aliases, its body, and whether it memoizes.
3
+ class Definition
4
+ attr_reader canonical: Symbol
5
+ attr_reader aliases: Array[Symbol]
6
+ attr_reader body: Proc
7
+ attr_reader raw_name: Symbol
8
+
9
+ # The `[file, line]` the compiled methods report; `Builder#namespace` overwrites it.
10
+ attr_accessor source_location: [ String, Integer ]
11
+
12
+ @memoized: bool
13
+
14
+ def initialize: (Symbol canonical, Array[Symbol] aliases, Proc body, [ String, Integer ] source_location) -> void
15
+ def initialize_copy: (Definition other) -> void
16
+ def memoize!: () -> void
17
+ def memoized?: () -> bool
18
+ def names: () -> Array[Symbol]
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Briefly
2
+ # An ordered list of `rescue_from` registrations, queried at dispatch time.
3
+ class ErrorRegistry
4
+ # A single registration. `names` is nil for facade-wide/global handlers.
5
+ class Entry < ::Struct[untyped]
6
+ attr_accessor klass: Class
7
+ attr_accessor names: Array[Symbol]?
8
+ attr_accessor handler: Briefly::handler
9
+
10
+ def self.new: (Class klass, Array[Symbol]? names, Briefly::handler handler) -> Entry
11
+ end
12
+
13
+ @entries: Array[Entry]
14
+ @mutex: Thread::Mutex
15
+
16
+ def initialize: () -> void
17
+ def add: (Class klass, Array[Symbol]? names, Briefly::handler handler) -> self
18
+ def scoped: (Symbol name) -> Array[Entry]
19
+ def wide: () -> Array[Entry]
20
+ def clear: () -> self
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Briefly
2
+ # The object returned by `Briefly.define`. Shortcuts are compiled onto its singleton class as real
3
+ # methods; RBS cannot see them, so they do not appear here.
4
+ class Facade
5
+ # Names a shortcut may not take.
6
+ RESERVED: Array[Symbol]
7
+
8
+ @__defs: Hash[Symbol, Definition]
9
+ @__aliases: Hash[Symbol, Symbol]
10
+ @__memos: Hash[Symbol, untyped]
11
+ @__monitor: Monitor
12
+ @__errors: ErrorRegistry
13
+ @__public: Array[Symbol]
14
+ @__children: Hash[Symbol, Facade]
15
+
16
+ def initialize: () -> void
17
+ def shortcuts: () -> Array[Symbol]
18
+ def shortcut?: (Symbol name) -> bool
19
+ def clear_memos!: () -> self
20
+ def reset!: () -> self
21
+ def configure: () ?{ (?) [self: Builder] -> void } -> self
22
+ def inspect: () -> String
23
+ def to_s: () -> String
24
+
25
+ private
26
+
27
+ def __prepare: () -> Builder
28
+ def __commit: (Briefly::plan plan) -> self
29
+ def __install: (Hash[Symbol, Definition] defs, Array[Briefly::error_entry] error_entries) -> self
30
+ def __remove_methods: (Array[Symbol] names) -> void
31
+ def __define: (Definition defn) -> void
32
+ def __call: (Symbol name, *untyped, **untyped) ?{ (?) -> untyped } -> untyped
33
+ def __memo: (Symbol name) { () -> untyped } -> untyped
34
+ def __handle: (StandardError error, Symbol name) -> untyped
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module Briefly
2
+ # Shortcut pack for Rails applications. Loaded only when `Briefly::Rails` is referenced.
3
+ #
4
+ # An umbrella over `Config`, `Env`, `View` and `Reload`, plus `DB` under the `db` namespace. Each
5
+ # nested pack exposes `install` and nothing else.
6
+ module Rails
7
+ def self?.install: (Builder builder) -> Builder
8
+
9
+ # Configuration, paths and the application-wide singletons.
10
+ module Config
11
+ def self?.install: (Builder builder) -> Builder
12
+ end
13
+
14
+ # The environment and its predicates.
15
+ module Env
16
+ def self?.install: (Builder builder) -> Builder
17
+ end
18
+
19
+ # Helpers, routes and rendering outside a controller.
20
+ module View
21
+ def self?.install: (Builder builder) -> Builder
22
+ end
23
+
24
+ # One database, reached through one Active Record class. `base` is a constant path resolved on
25
+ # every call, so a reloaded class is never captured.
26
+ module DB
27
+ def self?.install: (Builder builder, ?base: String | Symbol | Module) -> Builder
28
+ end
29
+ end
30
+ end
data/sig/briefly.rbs ADDED
@@ -0,0 +1,57 @@
1
+ # A terse, curated facade over an application's most frequently reached-for objects.
2
+ module Briefly
3
+ VERSION: String
4
+
5
+ # Distinguishes "not memoized yet" from a memoized `nil`.
6
+ UNSET: Object
7
+
8
+ # Base class for every error raised by Briefly.
9
+ class Error < StandardError
10
+ end
11
+
12
+ # Raised when `memoize` or `rescue_from` names a shortcut that does not exist.
13
+ class UnknownShortcutError < Error
14
+ end
15
+
16
+ # Raised when a shortcut name or alias would shadow a facade method.
17
+ class ReservedNameError < Error
18
+ end
19
+
20
+ # Raised when `use` names a pack that is not registered.
21
+ class UnknownPackError < Error
22
+ end
23
+
24
+ # A recovery block. Its return value becomes the shortcut's return value.
25
+ type handler = ^(StandardError error, Symbol shortcut_name) -> untyped
26
+
27
+ # A registered error handler, before `ErrorRegistry` receives it.
28
+ type error_entry = [ Class, Array[Symbol]?, handler ]
29
+
30
+ # A validated `configure` pass, ready to install: definitions, error handlers, namespace children,
31
+ # and one `[child, plan]` pair per namespace this pass collected. Recursive, so RBS types the tail
32
+ # as `untyped`.
33
+ type plan = [ Hash[Symbol, Definition], Array[error_entry], Hash[Symbol, Facade], Array[[ Facade, untyped ]] ]
34
+
35
+ # A pack is any object responding to `#install(builder)`. Options are optional: Ruby drops an empty
36
+ # `**` splat, so a pack that takes none needs no `**opts` parameter.
37
+ interface _Pack
38
+ def install: (Builder builder, **untyped opts) -> untyped
39
+ end
40
+
41
+ # A pack, or a name `Briefly.register`'d for one.
42
+ type pack_ref = _Pack | String | Symbol
43
+
44
+ # A registry entry: a pack, or a constant path resolved on first use.
45
+ type pack_entry = _Pack | String
46
+
47
+ self.@errors: ErrorRegistry
48
+ self.@packs: Hash[String, pack_entry]
49
+
50
+ # Shortcuts are compiled at runtime, so RBS cannot see them. Declare the ones you rely on in
51
+ # your own `sig/`, e.g. `App: Briefly::Facade` plus `def App.config: () -> untyped`.
52
+ def self.define: () ?{ (?) [self: Builder] -> void } -> Facade
53
+ def self.register: (String | Symbol name, pack_entry pack) -> Module
54
+ def self.pack: (String | Symbol name) -> _Pack
55
+ def self.rescue_from: (Class error_class) { (StandardError, Symbol) -> untyped } -> Module
56
+ def self.errors: () -> ErrorRegistry
57
+ end
data/sig/manifest.yaml ADDED
@@ -0,0 +1,4 @@
1
+ # Standard-library signatures these RBS files reference. Consumers' RBS/Steep load these
2
+ # automatically when they depend on briefly. (The gem has no runtime gem dependencies.)
3
+ dependencies:
4
+ - name: monitor # Facade's reentrant memo lock
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: briefly
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Leonid Svyatov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: candor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: 0.2.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.2.0
26
+ description: Declare one facade object per application area and reach your framework,
27
+ config and clients through short, real, introspectable methods. One runtime dependency
28
+ (candor), correct under Puma and Rails code reloading, with a batteries-included
29
+ Rails pack.
30
+ email:
31
+ - leonid@svyatov.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - ".yardopts"
37
+ - CHANGELOG.md
38
+ - LICENSE.txt
39
+ - README.md
40
+ - briefly.gemspec
41
+ - lib/briefly.rb
42
+ - lib/briefly/builder.rb
43
+ - lib/briefly/definition.rb
44
+ - lib/briefly/error_registry.rb
45
+ - lib/briefly/errors.rb
46
+ - lib/briefly/facade.rb
47
+ - lib/briefly/rails.rb
48
+ - lib/briefly/rails/db.rb
49
+ - lib/briefly/rails/reload.rb
50
+ - lib/briefly/version.rb
51
+ - sig/briefly.rbs
52
+ - sig/briefly/builder.rbs
53
+ - sig/briefly/definition.rbs
54
+ - sig/briefly/error_registry.rbs
55
+ - sig/briefly/facade.rbs
56
+ - sig/briefly/rails.rbs
57
+ - sig/manifest.yaml
58
+ homepage: https://github.com/svyatov/briefly
59
+ licenses:
60
+ - MIT
61
+ metadata:
62
+ rubygems_mfa_required: 'true'
63
+ documentation_uri: https://rubydoc.info/gems/briefly
64
+ source_code_uri: https://github.com/svyatov/briefly
65
+ changelog_uri: https://github.com/svyatov/briefly/blob/main/CHANGELOG.md
66
+ bug_tracker_uri: https://github.com/svyatov/briefly/issues
67
+ rdoc_options: []
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.2.0
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubygems_version: 4.0.12
82
+ specification_version: 4
83
+ summary: A terse, curated facade over your application's most reached-for objects.
84
+ test_files: []