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.
- checksums.yaml +7 -0
- data/.yardopts +6 -0
- data/CHANGELOG.md +43 -0
- data/LICENSE.txt +21 -0
- data/README.md +445 -0
- data/briefly.gemspec +31 -0
- data/lib/briefly/builder.rb +174 -0
- data/lib/briefly/definition.rb +64 -0
- data/lib/briefly/error_registry.rb +40 -0
- data/lib/briefly/errors.rb +18 -0
- data/lib/briefly/facade.rb +159 -0
- data/lib/briefly/rails/db.rb +52 -0
- data/lib/briefly/rails/reload.rb +39 -0
- data/lib/briefly/rails.rb +91 -0
- data/lib/briefly/version.rb +6 -0
- data/lib/briefly.rb +94 -0
- data/sig/briefly/builder.rbs +28 -0
- data/sig/briefly/definition.rbs +20 -0
- data/sig/briefly/error_registry.rbs +22 -0
- data/sig/briefly/facade.rbs +36 -0
- data/sig/briefly/rails.rbs +30 -0
- data/sig/briefly.rbs +57 -0
- data/sig/manifest.yaml +4 -0
- metadata +84 -0
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
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: []
|