fmt 0.1.2 → 0.3.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 +4 -4
- data/README.md +170 -87
- data/lib/fmt/boot.rb +50 -0
- data/lib/fmt/lru_cache.rb +181 -0
- data/lib/fmt/mixins/matchable.rb +26 -0
- data/lib/fmt/models/arguments.rb +194 -0
- data/lib/fmt/models/embed.rb +48 -0
- data/lib/fmt/models/macro.rb +58 -0
- data/lib/fmt/models/model.rb +66 -0
- data/lib/fmt/models/pipeline.rb +47 -0
- data/lib/fmt/models/template.rb +55 -0
- data/lib/fmt/node.rb +128 -0
- data/lib/fmt/parsers/arguments_parser.rb +43 -0
- data/lib/fmt/parsers/embed_parser.rb +54 -0
- data/lib/fmt/parsers/macro_parser.rb +113 -0
- data/lib/fmt/parsers/parser.rb +56 -0
- data/lib/fmt/parsers/pipeline_parser.rb +41 -0
- data/lib/fmt/parsers/template_parser.rb +125 -0
- data/lib/fmt/refinements/kernel_refinement.rb +38 -0
- data/lib/fmt/registries/native_registry.rb +66 -0
- data/lib/fmt/registries/rainbow_registry.rb +36 -0
- data/lib/fmt/registries/registry.rb +127 -0
- data/lib/fmt/renderer.rb +132 -0
- data/lib/fmt/sigils.rb +23 -0
- data/lib/fmt/token.rb +126 -0
- data/lib/fmt/tokenizer.rb +96 -0
- data/lib/fmt/version.rb +3 -1
- data/lib/fmt.rb +51 -11
- data/sig/generated/fmt/boot.rbs +2 -0
- data/sig/generated/fmt/lru_cache.rbs +122 -0
- data/sig/generated/fmt/mixins/matchable.rbs +18 -0
- data/sig/generated/fmt/models/arguments.rbs +115 -0
- data/sig/generated/fmt/models/embed.rbs +34 -0
- data/sig/generated/fmt/models/macro.rbs +37 -0
- data/sig/generated/fmt/models/model.rbs +45 -0
- data/sig/generated/fmt/models/pipeline.rbs +31 -0
- data/sig/generated/fmt/models/template.rbs +33 -0
- data/sig/generated/fmt/node.rbs +64 -0
- data/sig/generated/fmt/parsers/arguments_parser.rbs +25 -0
- data/sig/generated/fmt/parsers/embed_parser.rbs +36 -0
- data/sig/generated/fmt/parsers/macro_parser.rbs +60 -0
- data/sig/generated/fmt/parsers/parser.rbs +44 -0
- data/sig/generated/fmt/parsers/pipeline_parser.rbs +25 -0
- data/sig/generated/fmt/parsers/template_parser.rbs +50 -0
- data/sig/generated/fmt/refinements/kernel_refinement.rbs +23 -0
- data/sig/generated/fmt/registries/native_registry.rbs +19 -0
- data/sig/generated/fmt/registries/rainbow_registry.rbs +11 -0
- data/sig/generated/fmt/registries/registry.rbs +69 -0
- data/sig/generated/fmt/renderer.rbs +70 -0
- data/sig/generated/fmt/sigils.rbs +30 -0
- data/sig/generated/fmt/token.rbs +77 -0
- data/sig/generated/fmt/tokenizer.rbs +51 -0
- data/sig/generated/fmt/version.rbs +5 -0
- data/sig/generated/fmt.rbs +41 -0
- metadata +126 -16
- data/lib/fmt/embed.rb +0 -19
- data/lib/fmt/filter.rb +0 -32
- data/lib/fmt/filters.rb +0 -76
- data/lib/fmt/formatter.rb +0 -50
- data/lib/fmt/scanners/base_scanner.rb +0 -41
- data/lib/fmt/scanners/embed_scanner.rb +0 -56
- data/lib/fmt/scanners/filter_scanner.rb +0 -31
- data/lib/fmt/scanners/key_scanner.rb +0 -15
- data/lib/fmt/scanners.rb +0 -3
- data/lib/fmt/transformer.rb +0 -63
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c91008418cc9c29be346581fda313f6a498203899c932480187bb9f70bf4fb3f
|
4
|
+
data.tar.gz: 77925da927d90b09fdba3edcd595dc392f43286d4e8468792038bd1f57242cd7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7c440974ee5dcf7a3ef53cbbccf021f4011890f9f2d279e91aa9affb08f9032b034d9e39e3385b40a7e3988c399d0a96dff074581b8ac2ea037f081b6877fe0c
|
7
|
+
data.tar.gz: f45953ec9a6095cdf98106ca5582a24631ff2262efef979c7e5cacbd0abaf686cd59a814aa217428e87d2064060e8b5af1aa5ec97b8d40124fab65e124841a84
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
<p align="center">
|
2
2
|
<a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
|
3
|
-
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-
|
3
|
+
<img alt="Lines of Code" src="https://img.shields.io/badge/loc-1039-47d299.svg" />
|
4
4
|
</a>
|
5
5
|
<a href="https://github.com/testdouble/standard">
|
6
6
|
<img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
|
@@ -13,165 +13,248 @@
|
|
13
13
|
</a>
|
14
14
|
</p>
|
15
15
|
|
16
|
-
# Fmt
|
16
|
+
# Fmt: Your Friendly String Formatter
|
17
17
|
|
18
|
-
|
18
|
+
Welcome to Fmt, your go-to companion for Ruby string templating – simple, versatile, and surprisingly powerful!
|
19
19
|
|
20
20
|
<!-- Tocer[start]: Auto-generated, don't remove. -->
|
21
21
|
|
22
22
|
## Table of Contents
|
23
23
|
|
24
|
-
- [
|
25
|
-
- [
|
26
|
-
- [Usage](#usage)
|
27
|
-
- [
|
28
|
-
- [
|
29
|
-
- [
|
30
|
-
|
24
|
+
- [What's Fmt, and why should you care?](#whats-fmt-and-why-should-you-care)
|
25
|
+
- [Getting Started: It's a Breeze!](#getting-started-its-a-breeze)
|
26
|
+
- [Usage: Your Formatting Adventure Begins](#usage-your-formatting-adventure-begins)
|
27
|
+
- [Macros: The Secret Sauce](#macros-the-secret-sauce)
|
28
|
+
- [Pipelines: Unleashing the Power of Chained Macros](#pipelines-unleashing-the-power-of-chained-macros)
|
29
|
+
- [Supported Methods: The Ultimate Answer to Formatting](#supported-methods-the-ultimate-answer-to-formatting)
|
30
|
+
- [Rainbow GEM: The Color of Magic](#rainbow-gem-the-color-of-magic)
|
31
|
+
- [Composition: The Art of the Template](#composition-the-art-of-the-template)
|
32
|
+
- [Embedded Templates: Nesting with Infinite Possibilities](#embedded-templates-nesting-with-infinite-possibilities)
|
33
|
+
- [Customizing Fmt: Create Your Own Extensions](#customizing-fmt-create-your-own-extensions)
|
34
|
+
- [Performance: Fast Formatting](#performance-fast-formatting)
|
35
|
+
- [Sponsors: Our Awesome Supporters](#sponsors-our-awesome-supporters)
|
31
36
|
|
32
37
|
<!-- Tocer[finish]: Auto-generated, don't remove. -->
|
33
38
|
|
34
|
-
##
|
39
|
+
## What's Fmt, and why should you care?
|
35
40
|
|
36
|
-
|
41
|
+
Fmt is a template engine that harnesses the raw power of Ruby's native string formatting mechanics. It's like having a universal translator for your strings, turning your formatting wishes into beautiful, functional output.
|
37
42
|
|
38
|
-
|
43
|
+
- 🚀 Supercharge your general-purpose templating
|
44
|
+
- 🎨 Craft CLI applications so beautiful, they'll make even the most stoic developer smile
|
45
|
+
- 🧠 Intuitive enough for beginners, powerful enough for experts
|
39
46
|
|
40
|
-
|
41
|
-
|
47
|
+
## Getting Started: It's a Breeze!
|
48
|
+
|
49
|
+
First, let's get you set up. It's easier than making a cup of coffee!
|
50
|
+
|
51
|
+
```sh
|
52
|
+
bundle add rainbow # <- optional, but recommended for those who enjoy a splash of color
|
42
53
|
bundle add fmt
|
43
54
|
```
|
44
55
|
|
45
|
-
|
46
|
-
|
47
|
-
Simply create a string with embedded formatting syntax as you'd normally do with `sprintf` or `format`.
|
56
|
+
Then, in your Ruby file:
|
48
57
|
|
49
58
|
```ruby
|
50
|
-
"
|
59
|
+
require "rainbow" # <- optional, but why not add some color to your life?
|
60
|
+
require "fmt"
|
51
61
|
```
|
52
62
|
|
53
|
-
|
63
|
+
## Usage: Your Formatting Adventure Begins
|
54
64
|
|
55
|
-
|
56
|
-
"%{...}FILTER|FILTER|FILTER"
|
57
|
-
```
|
65
|
+
Using Fmt is simpler than ordering takeout. Just create a string with Ruby's [format specifiers](https://ruby-doc.org/3.3.5/format_specifications_rdoc.html), much like you would for `sprintf`.
|
58
66
|
|
59
|
-
|
60
|
-
|
67
|
+
- `"%s"` - The classic
|
68
|
+
- `"%{variable}"` - For when you're feeling descriptive
|
69
|
+
- `"%<variable>s"` - When you want to be extra explicit
|
61
70
|
|
62
|
-
|
63
|
-
Also, you can use Rainbow filters like `bold`, `cyan`, `underline`, et al. if you have the [Rainbow GEM](https://github.com/ku1ik/rainbow) installed.
|
71
|
+
Remember, with Fmt, any string can be a template. It's like having a Swiss Army knife for text formatting!
|
64
72
|
|
65
|
-
|
73
|
+
### Macros: The Secret Sauce
|
66
74
|
|
67
|
-
|
75
|
+
Formatting macros are what make Fmt special. Append them to your format specifiers like so:
|
68
76
|
|
69
|
-
|
77
|
+
<!-- test_e798c3 -->
|
70
78
|
|
71
79
|
```ruby
|
72
|
-
|
73
|
-
|
80
|
+
Fmt("%s|>capitalize", "hello world!") # => "Hello world!"
|
81
|
+
Fmt("%{msg}|>capitalize", msg: "hello world!") # => "Hello world!"
|
82
|
+
```
|
74
83
|
|
75
|
-
|
76
|
-
Fmt template, name: "World"
|
84
|
+
Macros can accept arguments.
|
77
85
|
|
78
|
-
|
86
|
+
<!-- test_1707d2 -->
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Fmt("%s|>prepend('Hello ')", "world!") # => "Hello world!"
|
90
|
+
Fmt("%{msg}|>prepend('Hello ')", msg: "world!") # => "Hello world!"
|
79
91
|
```
|
80
92
|
|
81
|
-
|
93
|
+
### Pipelines: Unleashing the Power of Chained Macros
|
94
|
+
|
95
|
+
Macros can be chained to create a formatting pipeline.
|
82
96
|
|
83
|
-
|
97
|
+
<!-- test_425625 -->
|
84
98
|
|
85
99
|
```ruby
|
86
|
-
|
87
|
-
|
100
|
+
Fmt("%s|>prepend('Hello ')|>ljust(32, '.')|>upcase", "world!") # => "HELLO WORLD!...................."
|
101
|
+
Fmt("%{msg}|>prepend('Hello ')|>ljust(32, '.')|>upcase", msg: "world!") # => "HELLO WORLD!...................."
|
102
|
+
```
|
103
|
+
|
104
|
+
> [!NOTE]
|
105
|
+
> Pipelines are processed left to right. The return value from the preceeding macro is the starting value for the next macro.
|
88
106
|
|
89
|
-
|
90
|
-
Fmt template, date: Time.now
|
107
|
+
Arguments and return values can be any type.
|
91
108
|
|
92
|
-
|
109
|
+
<!-- test_f55ae2 -->
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
Fmt("%p|>partition(/:/)|>last|>delete_suffix('>')", Object.new) # => "0x000000011f33bc68"
|
93
113
|
```
|
94
114
|
|
95
|
-
|
115
|
+
### Supported Methods: The Ultimate Answer to Formatting
|
96
116
|
|
97
|
-
|
117
|
+
Most public instance methods on the following classes are supported.
|
98
118
|
|
99
|
-
|
100
|
-
|
101
|
-
|
119
|
+
- `Array`
|
120
|
+
- `Date`
|
121
|
+
- `DateTime`
|
122
|
+
- `FalseClass`
|
123
|
+
- `Float`
|
124
|
+
- `Hash`
|
125
|
+
- `Integer`
|
126
|
+
- `NilClass`
|
127
|
+
- `Range`
|
128
|
+
- `Regexp`
|
129
|
+
- `Set`
|
130
|
+
- `StandardError`
|
131
|
+
- `String`
|
132
|
+
- `Struct`
|
133
|
+
- `Symbol`
|
134
|
+
- `Time`
|
135
|
+
- `TrueClass`
|
102
136
|
|
103
|
-
|
104
|
-
|
137
|
+
> [!TIP]
|
138
|
+
> If you're using libraries like ActiveSupport that extend these classes, extension methods will also available if the library is required before Fmt.
|
105
139
|
|
106
|
-
|
140
|
+
#### Rainbow GEM: The Color of Magic
|
107
141
|
|
108
|
-
|
109
|
-
T
|
142
|
+
Color and style support is available if your project includes the [Rainbow GEM](https://github.com/ku1ik/rainbow).
|
110
143
|
|
111
|
-
|
144
|
+
<!-- test_19c8ca -->
|
112
145
|
|
113
|
-
|
146
|
+
```ruby
|
147
|
+
template = "%{msg}|>cyan|>bold|>underline"
|
148
|
+
Fmt(template, msg: "Hello World!")
|
149
|
+
#=> "\e[36m\e[1m\e[4mHello World!\e[0m"
|
114
150
|
```
|
115
151
|
|
116
|
-
|
152
|
+
### Composition: The Art of the Template
|
153
|
+
|
154
|
+
You can mix and match macros that target any type within a pipeline.
|
117
155
|
|
118
|
-
|
156
|
+
Templates can also include multiple format strings with their own distinct pipelines.
|
119
157
|
|
120
|
-
|
121
|
-
The block accepts a string and should return a replacement string.
|
158
|
+
<!-- test_0dbfcd -->
|
122
159
|
|
123
160
|
```ruby
|
124
|
-
|
125
|
-
|
161
|
+
template = "Date: %<date>.10s|>magenta -- %{msg}|>titleize|>bold"
|
162
|
+
Fmt(template, date: Time.now, msg: "this is cool")
|
163
|
+
#=> "Date: \e[35m2024-09-20\e[0m \e[1mThis Is Cool\e[0m"
|
164
|
+
```
|
126
165
|
|
127
|
-
|
166
|
+
#### Embedded Templates: Nesting with Infinite Possibilities
|
128
167
|
|
129
|
-
|
130
|
-
%{head}ljust|faint
|
131
|
-
%{message}bold
|
132
|
-
%{tail}ljust|faint
|
133
|
-
T
|
168
|
+
Embedded templates can be nested within other templates... as deep as needed!
|
134
169
|
|
135
|
-
|
170
|
+
<!-- test_efee7a -->
|
136
171
|
|
137
|
-
|
172
|
+
```ruby
|
173
|
+
template = "%{msg}|>faint {{%{embed}|>bold}}"
|
174
|
+
Fmt(template, msg: "Look Ma...", embed: "I'm embedded!")
|
175
|
+
#=> "\e[2mLook Ma...\e[0m \e[1mI'm embedded!\e[0m"
|
138
176
|
```
|
139
177
|
|
140
|
-
|
178
|
+
Embeds can also have their own distinct pipelines.
|
141
179
|
|
142
|
-
|
143
|
-
|
144
|
-
Templates can be embedded or nested within other templates... as deep as needed!
|
145
|
-
Just wrap the embedded template in double curly braces: `{{EMBEDDED TEMPLATE HERE}}`
|
180
|
+
<!-- test_abb7ea -->
|
146
181
|
|
147
182
|
```ruby
|
148
|
-
|
149
|
-
|
183
|
+
template = "%{msg}|>faint {{%{embed}|>bold}}|>underline"
|
184
|
+
Fmt(template, msg: "Look Ma...", embed: "I'm embedded!")
|
185
|
+
#=> "\e[2mLook Ma...\e[0m \e[1m\e[4mI'm embedded!\e[0m"
|
186
|
+
```
|
150
187
|
|
151
|
-
|
152
|
-
Fmt template, value: "Outer", embed_value: "Inner"
|
188
|
+
Embeds can be deeply nested.
|
153
189
|
|
154
|
-
|
190
|
+
<!-- test_79e924 -->
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
template = "%{msg}|>faint {{%{embed}|>bold {{%{deep_embed}|>red|>bold}}}}"
|
194
|
+
Fmt(template, msg: "Look Ma...", embed: "I'm embedded!", deep_embed: "And I'm deeply embedded!")
|
195
|
+
#=> "\e[2mLook Ma...\e[0m \e[1mI'm embedded!\e[0m \e[31m\e[1mAnd I'm deeply embedded!\e[0m"
|
155
196
|
```
|
156
197
|
|
157
|
-
|
198
|
+
Embeds can also span multiple lines.
|
199
|
+
|
200
|
+
<!-- test_054526 -->
|
158
201
|
|
159
202
|
```ruby
|
160
203
|
template = <<~T
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
204
|
+
Multiline:
|
205
|
+
%{one}|>red {{
|
206
|
+
%{two}|>blue {{
|
207
|
+
%{three}|>green
|
208
|
+
}}
|
209
|
+
}}|>bold
|
165
210
|
T
|
211
|
+
Fmt(template, one: "Red", two: "Blue", three: "Green")
|
212
|
+
#=> "Multiline:\n\e[31mRed\e[0m \e[1m\n \e[34mBlue\e[0m \n \e[32mGreen\e[0m"
|
213
|
+
```
|
214
|
+
|
215
|
+
### Customizing Fmt: Create Your Own Extensions
|
216
|
+
|
217
|
+
Want to add your own filters? It's easier than you might think:
|
166
218
|
|
167
|
-
|
219
|
+
<!-- test_2cacce -->
|
168
220
|
|
169
|
-
|
221
|
+
```ruby
|
222
|
+
Fmt.register([Object, :shuffle]) { |*args, **kwargs| to_s.chars.shuffle.join }
|
223
|
+
Fmt("%s|>shuffle", "This don't make no sense.")
|
224
|
+
#=> "de.nnoTtsnh'oeek ssim a "
|
170
225
|
```
|
171
226
|
|
172
|
-
|
227
|
+
You can also run a Ruby block with temporary filters if you don't want to officially register them.
|
228
|
+
|
229
|
+
> [!TIP]
|
230
|
+
> This also allows you to override existing filters for the duration of the block.
|
231
|
+
|
232
|
+
<!-- test_7df4eb -->
|
233
|
+
|
234
|
+
```ruby
|
235
|
+
Fmt.with_overrides([Object, :red] => proc { |*args, **kwargs| Rainbow(self).crimson.bold }) do
|
236
|
+
Fmt("%s|>red", "This is customized red!")
|
237
|
+
#=> "\e[38;5;197m\e[1mThis is customized red!\e[0m"
|
238
|
+
end
|
239
|
+
|
240
|
+
Fmt("%s|>red", "This is original red!")
|
241
|
+
#=> "\e[31mThis is original red!\e[0m"
|
242
|
+
```
|
243
|
+
|
244
|
+
## Performance: Fast Formatting
|
245
|
+
|
246
|
+
Fmt isn't just pretty – it's quick too!
|
247
|
+
|
248
|
+
- Tokenization: Uses StringScanner and Ripper to parse and tokenize templates
|
249
|
+
- Caching: Stores an Abstract Syntax Tree (AST) representation of each template, pipeline, and macro
|
250
|
+
- Speed: Current benchmarks show an average pipeline execution time of **under <0.3 milliseconds**
|
251
|
+
|
252
|
+
> [!NOTE]
|
253
|
+
> While Fmt is optimized for performance, remember that complex pipelines might take a tad longer.
|
254
|
+
|
255
|
+
**Happy formatting!**
|
173
256
|
|
174
|
-
## Sponsors
|
257
|
+
## Sponsors: Our Awesome Supporters
|
175
258
|
|
176
259
|
<p align="center">
|
177
260
|
<em>Proudly sponsored by</em>
|
data/lib/fmt/boot.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
# Standard libraries
|
6
|
+
require "date"
|
7
|
+
require "forwardable"
|
8
|
+
require "monitor"
|
9
|
+
require "ripper"
|
10
|
+
require "securerandom"
|
11
|
+
require "set"
|
12
|
+
require "singleton"
|
13
|
+
require "strscan"
|
14
|
+
|
15
|
+
# 3rd party libraries
|
16
|
+
require "ast"
|
17
|
+
|
18
|
+
# Foundational files (globals)
|
19
|
+
require_relative "sigils"
|
20
|
+
require_relative "lru_cache"
|
21
|
+
require_relative "mixins/matchable"
|
22
|
+
require_relative "node"
|
23
|
+
require_relative "renderer"
|
24
|
+
require_relative "token"
|
25
|
+
require_relative "tokenizer"
|
26
|
+
require_relative "version"
|
27
|
+
|
28
|
+
# Refinements
|
29
|
+
require_relative "refinements/kernel_refinement"
|
30
|
+
|
31
|
+
# Registries -- store of Procs that can be used with Fmt
|
32
|
+
require_relative "registries/registry" # <- base class
|
33
|
+
require_relative "registries/native_registry"
|
34
|
+
require_relative "registries/rainbow_registry"
|
35
|
+
|
36
|
+
# Parsers -- String | Object parsers that generate ASTs
|
37
|
+
require_relative "parsers/parser" # <- base class
|
38
|
+
require_relative "parsers/arguments_parser"
|
39
|
+
require_relative "parsers/macro_parser"
|
40
|
+
require_relative "parsers/embed_parser"
|
41
|
+
require_relative "parsers/pipeline_parser"
|
42
|
+
require_relative "parsers/template_parser"
|
43
|
+
|
44
|
+
# Models -- data structures build from ASTs
|
45
|
+
require_relative "models/model" # <- base class
|
46
|
+
require_relative "models/arguments"
|
47
|
+
require_relative "models/embed"
|
48
|
+
require_relative "models/macro"
|
49
|
+
require_relative "models/pipeline"
|
50
|
+
require_relative "models/template"
|
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Fmt
|
6
|
+
# A threadsafe fixed-size LRU in-memory cache
|
7
|
+
# Grows to capacity then evicts the least used entries
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# cache = Fmt::Cache.new
|
11
|
+
#
|
12
|
+
# cache.put :key, "value"
|
13
|
+
# cache.get :key
|
14
|
+
# cache.delete :key
|
15
|
+
# cache.fetch :key, "default"
|
16
|
+
# cache.fetch(:key) { "default" }
|
17
|
+
#
|
18
|
+
# @example Capacity
|
19
|
+
# Fmt::Cache.capacity = 10_000
|
20
|
+
class LRUCache
|
21
|
+
include MonitorMixin
|
22
|
+
|
23
|
+
DEFAULT_CAPACITY = 5_000 # : Integer -- default capacity
|
24
|
+
|
25
|
+
# Constructor
|
26
|
+
# @rbs capacity: Integer -- max capacity (negative values are uncapped, default: 5_000)
|
27
|
+
# @rbs return: Fmt::Cache
|
28
|
+
def initialize(capacity: DEFAULT_CAPACITY)
|
29
|
+
super()
|
30
|
+
@capacity = capacity
|
31
|
+
@store = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# The cache max capacity (number of entries)
|
35
|
+
# @rbs return: Integer
|
36
|
+
def capacity
|
37
|
+
synchronize { @capacity }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Set the max capacity (number of entries)
|
41
|
+
# @rbs capacity: Integer -- new max capacity
|
42
|
+
# @rbs return: Integer -- new max capacity
|
43
|
+
def capacity=(capacity)
|
44
|
+
synchronize { @capacity = capacity.to_i }
|
45
|
+
end
|
46
|
+
|
47
|
+
# Indicates if the cache is capped
|
48
|
+
# @rbs return: bool
|
49
|
+
def capped?
|
50
|
+
synchronize { capacity >= 0 }
|
51
|
+
end
|
52
|
+
|
53
|
+
# Clears the cache
|
54
|
+
# @rbs return: void
|
55
|
+
def clear
|
56
|
+
synchronize { store.clear }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Deletes the entry for the specified key
|
60
|
+
# @rbs key: Object -- key to delete
|
61
|
+
# @rbs return: Object? -- the deleted value
|
62
|
+
def delete(key)
|
63
|
+
synchronize { store.delete key }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Fetches the value for the specified key
|
67
|
+
# Writes the default value if the key is not found
|
68
|
+
# @rbs key: Object -- key to fetch
|
69
|
+
# @rbs default: Object -- default value to write
|
70
|
+
# @rbs block: Proc -- block to call to get the default value
|
71
|
+
# @rbs return: Object -- value
|
72
|
+
def fetch(key, default = nil, &block)
|
73
|
+
return get(key) if key?(key)
|
74
|
+
default ||= block&.call
|
75
|
+
synchronize { put key, default }
|
76
|
+
end
|
77
|
+
|
78
|
+
# Fetches a value from the cache without synchronization (not thread safe)
|
79
|
+
# @rbs key: Object -- key to fetch
|
80
|
+
# @rbs default: Object -- default value to write
|
81
|
+
# @rbs block: Proc -- block to call to get the default value
|
82
|
+
# @rbs return: Object -- value
|
83
|
+
def fetch_unsafe(key, default = nil, &block)
|
84
|
+
return store[key] if store.key?(key)
|
85
|
+
store[key] = (default || block&.call)
|
86
|
+
end
|
87
|
+
|
88
|
+
# Indicates if the cache is full
|
89
|
+
# @rbs return: bool
|
90
|
+
def full?
|
91
|
+
synchronize { capped? && store.size > capacity }
|
92
|
+
end
|
93
|
+
|
94
|
+
# Retrieves the value for the specified key
|
95
|
+
# @rbs key: Object -- key to retrieve
|
96
|
+
def get(key)
|
97
|
+
synchronize do
|
98
|
+
reposition(key) if key?(key)
|
99
|
+
store[key]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Cache keys
|
104
|
+
# @rbs return: Array[Object]
|
105
|
+
def keys
|
106
|
+
synchronize { store.keys }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Indicates if the cache contains the specified key
|
110
|
+
# @rbs key: Object -- key to check
|
111
|
+
# @rbs return: bool
|
112
|
+
def key?(key)
|
113
|
+
synchronize { store.key? key }
|
114
|
+
end
|
115
|
+
|
116
|
+
# Stores the value for the specified key
|
117
|
+
# @rbs key: Object -- key to store
|
118
|
+
# @rbs value: Object -- value to store
|
119
|
+
# @rbs return: Object -- value
|
120
|
+
def put(key, value)
|
121
|
+
synchronize do
|
122
|
+
delete key if capped? # keep keey fresh if capped
|
123
|
+
store[key] = value
|
124
|
+
store.shift if full? # resize the cache if necessary
|
125
|
+
value
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Resets the cache capacity to the default
|
130
|
+
# @rbs return: Integer -- capacity
|
131
|
+
def reset_capacity
|
132
|
+
synchronize { @capacity = DEFAULT_CAPACITY }
|
133
|
+
end
|
134
|
+
|
135
|
+
# The current size of the cache (number of entries)
|
136
|
+
# @rbs return: Integer
|
137
|
+
def size
|
138
|
+
synchronize { store.size }
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns a Hash with only the given keys
|
142
|
+
# @rbs keys: Array[Object] -- keys to include
|
143
|
+
# @rbs return: Hash[Object, Object]
|
144
|
+
def slice(*keys)
|
145
|
+
synchronize { store.slice(*keys) }
|
146
|
+
end
|
147
|
+
|
148
|
+
# Hash representation of the cache
|
149
|
+
# @rbs return: Hash[Object, Proc]
|
150
|
+
def to_h
|
151
|
+
synchronize { store.dup }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Cache values
|
155
|
+
# @rbs return: Array[Object]
|
156
|
+
def values
|
157
|
+
synchronize { store.values }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Executes a block with a synchronized mutex
|
161
|
+
# @rbs block: Proc -- block to execute
|
162
|
+
def lock(&block)
|
163
|
+
synchronize(&block)
|
164
|
+
end
|
165
|
+
|
166
|
+
alias_method :[], :get # : Object -- alias for get
|
167
|
+
alias_method :[]=, :put # : Object -- alias for put
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
attr_reader :store # : Hash[Object, Object]
|
172
|
+
|
173
|
+
# Moves the key to the end keeping it fresh
|
174
|
+
# @rbs key: Object -- key to reposition
|
175
|
+
# @rbs return: Object -- value
|
176
|
+
def reposition(key)
|
177
|
+
value = store.delete(key)
|
178
|
+
store[key] = value
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rbs_inline: enabled
|
4
|
+
|
5
|
+
module Fmt
|
6
|
+
module Matchable
|
7
|
+
# Hash representation of the Object (required for pattern matching)
|
8
|
+
# @rbs return: Hash[Symbol, Object]
|
9
|
+
def to_h
|
10
|
+
raise Error, "to_h must be implemented by including class"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns a Hash representation of the object limited to the given keys
|
14
|
+
# @rbs keys: Array[Symbol] -- keys to include
|
15
|
+
# @rbs return: Hash[Symbol, Object]
|
16
|
+
def deconstruct_keys(keys = [])
|
17
|
+
to_h.select { _1 in keys }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns an Array representation of the object
|
21
|
+
# @rbs return: Array[Object]
|
22
|
+
def deconstruct
|
23
|
+
to_h.values
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|