fmt 0.1.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +167 -93
  3. data/lib/fmt/boot.rb +50 -0
  4. data/lib/fmt/lru_cache.rb +181 -0
  5. data/lib/fmt/mixins/matchable.rb +26 -0
  6. data/lib/fmt/models/arguments.rb +194 -0
  7. data/lib/fmt/models/embed.rb +48 -0
  8. data/lib/fmt/models/macro.rb +58 -0
  9. data/lib/fmt/models/model.rb +66 -0
  10. data/lib/fmt/models/pipeline.rb +47 -0
  11. data/lib/fmt/models/template.rb +55 -0
  12. data/lib/fmt/node.rb +128 -0
  13. data/lib/fmt/parsers/arguments_parser.rb +43 -0
  14. data/lib/fmt/parsers/embed_parser.rb +54 -0
  15. data/lib/fmt/parsers/macro_parser.rb +113 -0
  16. data/lib/fmt/parsers/parser.rb +56 -0
  17. data/lib/fmt/parsers/pipeline_parser.rb +41 -0
  18. data/lib/fmt/parsers/template_parser.rb +125 -0
  19. data/lib/fmt/refinements/kernel_refinement.rb +38 -0
  20. data/lib/fmt/registries/native_registry.rb +66 -0
  21. data/lib/fmt/registries/rainbow_registry.rb +36 -0
  22. data/lib/fmt/registries/registry.rb +127 -0
  23. data/lib/fmt/renderer.rb +132 -0
  24. data/lib/fmt/sigils.rb +23 -0
  25. data/lib/fmt/token.rb +126 -0
  26. data/lib/fmt/tokenizer.rb +96 -0
  27. data/lib/fmt/version.rb +3 -1
  28. data/lib/fmt.rb +50 -12
  29. data/sig/generated/fmt/boot.rbs +2 -0
  30. data/sig/generated/fmt/lru_cache.rbs +122 -0
  31. data/sig/generated/fmt/mixins/matchable.rbs +18 -0
  32. data/sig/generated/fmt/models/arguments.rbs +115 -0
  33. data/sig/generated/fmt/models/embed.rbs +34 -0
  34. data/sig/generated/fmt/models/macro.rbs +37 -0
  35. data/sig/generated/fmt/models/model.rbs +45 -0
  36. data/sig/generated/fmt/models/pipeline.rbs +31 -0
  37. data/sig/generated/fmt/models/template.rbs +33 -0
  38. data/sig/generated/fmt/node.rbs +64 -0
  39. data/sig/generated/fmt/parsers/arguments_parser.rbs +25 -0
  40. data/sig/generated/fmt/parsers/embed_parser.rbs +36 -0
  41. data/sig/generated/fmt/parsers/macro_parser.rbs +60 -0
  42. data/sig/generated/fmt/parsers/parser.rbs +44 -0
  43. data/sig/generated/fmt/parsers/pipeline_parser.rbs +25 -0
  44. data/sig/generated/fmt/parsers/template_parser.rbs +50 -0
  45. data/sig/generated/fmt/refinements/kernel_refinement.rbs +23 -0
  46. data/sig/generated/fmt/registries/native_registry.rbs +19 -0
  47. data/sig/generated/fmt/registries/rainbow_registry.rbs +11 -0
  48. data/sig/generated/fmt/registries/registry.rbs +69 -0
  49. data/sig/generated/fmt/renderer.rbs +70 -0
  50. data/sig/generated/fmt/sigils.rbs +30 -0
  51. data/sig/generated/fmt/token.rbs +77 -0
  52. data/sig/generated/fmt/tokenizer.rbs +51 -0
  53. data/sig/generated/fmt/version.rbs +5 -0
  54. data/sig/generated/fmt.rbs +41 -0
  55. metadata +126 -18
  56. data/lib/fmt/embed.rb +0 -19
  57. data/lib/fmt/filter.rb +0 -32
  58. data/lib/fmt/filter_groups/filter_group.rb +0 -56
  59. data/lib/fmt/filter_groups/rainbow_filter_group.rb +0 -27
  60. data/lib/fmt/filter_groups/string_filter_group.rb +0 -28
  61. data/lib/fmt/formatter.rb +0 -60
  62. data/lib/fmt/scanners/base_scanner.rb +0 -41
  63. data/lib/fmt/scanners/embed_scanner.rb +0 -56
  64. data/lib/fmt/scanners/filter_scanner.rb +0 -31
  65. data/lib/fmt/scanners/key_scanner.rb +0 -15
  66. data/lib/fmt/scanners.rb +0 -3
  67. data/lib/fmt/transformer.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7aded878eeff57ab27f37b6e76bc34281d94000c95c5d0ae77dde2bee5ab5989
4
- data.tar.gz: 0d6390579afbfa629f0e53a127c25b60c04548b831e8ec13b9ebd8ed0880a238
3
+ metadata.gz: c91008418cc9c29be346581fda313f6a498203899c932480187bb9f70bf4fb3f
4
+ data.tar.gz: 77925da927d90b09fdba3edcd595dc392f43286d4e8468792038bd1f57242cd7
5
5
  SHA512:
6
- metadata.gz: 1235f7508b608760ce2f984cdcb321c01ac2a1cd3d7d5b9d1b586b45614e3b79c38bf900264b569fa469c17e34d2b6e1999984715a9b51c8caa792ee7d080aa9
7
- data.tar.gz: '012095a10cbeac34f3457bf18ebe9135513a774984139cd1c7c300cd00d774590eff1826f7af137adbb32acd77f2eb8bffa1d52b48c579422fe43a2e7c3769cf'
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-347-47d299.svg" />
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,174 +13,248 @@
13
13
  </a>
14
14
  </p>
15
15
 
16
- # Fmt
16
+ # Fmt: Your Friendly String Formatter
17
17
 
18
- #### A simple template engine based on native Ruby String formatting mechanics
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
- - [Why?](#why)
25
- - [Setup](#setup)
26
- - [Usage](#usage)
27
- - [Formatting](#formatting)
28
- - [Filters](#filters)
29
- - [Embeds](#embeds)
30
- - [Sponsors](#sponsors)
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
- ## Why?
39
+ ## What's Fmt, and why should you care?
35
40
 
36
- I'm currenly using this to help build beautiful CLI applications with Ruby. Plus it's fun.
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
- ## Setup
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
- bundle add rainbow # <- optional
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
- ## Usage
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
- Filters can be chained after the placeholder like so.
63
+ ## Usage: Your Formatting Adventure Begins
54
64
 
55
- ```ruby
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
- > [!NOTE]
60
- > Filters are processed in the order they are specified.
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
- Filters can be [native Ruby formatting](https://docs.ruby-lang.org/en/master/format_specifications_rdoc.html) as well as String methods like `capitalize`, `downcase`, `strip`, etc.
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
- **You can even [register your own filters](#filters).**
73
+ ### Macros: The Secret Sauce
66
74
 
67
- ### Formatting
75
+ Formatting macros are what make Fmt special. Append them to your format specifiers like so:
68
76
 
69
- Basic example:
77
+ <!-- test_e798c3 -->
70
78
 
71
79
  ```ruby
72
- require "rainbow"
73
- require "fmt"
80
+ Fmt("%s|>capitalize", "hello world!") # => "Hello world!"
81
+ Fmt("%{msg}|>capitalize", msg: "hello world!") # => "Hello world!"
82
+ ```
74
83
 
75
- Fmt.add_rainbow_filters
84
+ Macros can accept arguments.
76
85
 
77
- template = "Hello %{name}cyan|bold"
78
- Fmt template, name: "World"
86
+ <!-- test_1707d2 -->
79
87
 
80
- #=> "Hello \e[36m\e[1mWorld\e[0m"
88
+ ```ruby
89
+ Fmt("%s|>prepend('Hello ')", "world!") # => "Hello world!"
90
+ Fmt("%{msg}|>prepend('Hello ')", msg: "world!") # => "Hello world!"
81
91
  ```
82
92
 
83
- ![CleanShot 2024-07-26 at 01 40 33@2x](https://github.com/user-attachments/assets/04ff90e6-254a-42d4-9169-586ac24b82f0)
93
+ ### Pipelines: Unleashing the Power of Chained Macros
94
+
95
+ Macros can be chained to create a formatting pipeline.
84
96
 
85
- Mix and match native formatting with Rainbow formatting:
97
+ <!-- test_425625 -->
86
98
 
87
99
  ```ruby
88
- require "rainbow"
89
- require "fmt"
100
+ Fmt("%s|>prepend('Hello ')|>ljust(32, '.')|>upcase", "world!") # => "HELLO WORLD!...................."
101
+ Fmt("%{msg}|>prepend('Hello ')|>ljust(32, '.')|>upcase", msg: "world!") # => "HELLO WORLD!...................."
102
+ ```
90
103
 
91
- Fmt.add_rainbow_filters
104
+ > [!NOTE]
105
+ > Pipelines are processed left to right. The return value from the preceeding macro is the starting value for the next macro.
92
106
 
93
- template = "Date: %{date}.10s|magenta"
94
- Fmt template, date: Time.now
107
+ Arguments and return values can be any type.
95
108
 
96
- #=> "Date: \e[35m2024-07-26\e[0m"
97
- ```
109
+ <!-- test_f55ae2 -->
98
110
 
99
- ![CleanShot 2024-07-26 at 01 42 53@2x](https://github.com/user-attachments/assets/507913b0-826b-4526-9c79-27f766c904b3)
111
+ ```ruby
112
+ Fmt("%p|>partition(/:/)|>last|>delete_suffix('>')", Object.new) # => "0x000000011f33bc68"
113
+ ```
100
114
 
101
- Multiline example:
115
+ ### Supported Methods: The Ultimate Answer to Formatting
102
116
 
103
- ```ruby
104
- require "rainbow"
105
- require "fmt"
117
+ Most public instance methods on the following classes are supported.
106
118
 
107
- Fmt.add_rainbow_filters
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`
108
136
 
109
- template = <<~T
110
- Date: %{date}.10s|underline
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.
111
139
 
112
- Greetings, %{name}upcase|bold
140
+ #### Rainbow GEM: The Color of Magic
113
141
 
114
- %{message}strip|green
115
- T
142
+ Color and style support is available if your project includes the [Rainbow GEM](https://github.com/ku1ik/rainbow).
116
143
 
117
- Fmt template, date: Time.now, name: "Hopsoft", message: "This is neat!"
144
+ <!-- test_19c8ca -->
118
145
 
119
- #=> "Date: \e[4m2024-07-26\e[0m\n\nGreetings, \e[1mHOPSOFT\e[0m\n\n\e[32mThis is neat!\e[0m\n"
146
+ ```ruby
147
+ template = "%{msg}|>cyan|>bold|>underline"
148
+ Fmt(template, msg: "Hello World!")
149
+ #=> "\e[36m\e[1m\e[4mHello World!\e[0m"
120
150
  ```
121
151
 
122
- ![CleanShot 2024-07-26 at 01 44 30@2x](https://github.com/user-attachments/assets/8926009c-7cf1-4140-9a2a-6ed718d50926)
152
+ ### Composition: The Art of the Template
153
+
154
+ You can mix and match macros that target any type within a pipeline.
123
155
 
124
- ### Filters
156
+ Templates can also include multiple format strings with their own distinct pipelines.
125
157
 
126
- You can also add your own filters to Fmt by calling `Fmt.add_filter(:name, &block)`.
127
- The block accepts a string and should return a replacement string.
158
+ <!-- test_0dbfcd -->
128
159
 
129
160
  ```ruby
130
- require "rainbow"
131
- require "fmt"
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
+ ```
132
165
 
133
- Fmt.add_rainbow_filters
134
- Fmt.add_filter(:ljust) { |val| "".ljust 14, val.to_s }
166
+ #### Embedded Templates: Nesting with Infinite Possibilities
135
167
 
136
- template = <<~T
137
- %{head}ljust|faint
138
- %{message}bold
139
- %{tail}ljust|faint
140
- T
168
+ Embedded templates can be nested within other templates... as deep as needed!
141
169
 
142
- Fmt template, head: "#", message: "Give it a try!", tail: "#"
170
+ <!-- test_efee7a -->
143
171
 
144
- #=> "\e[2m##############\e[0m\n\e[1mGive it a try!\e[0m\n\e[2m##############\e[0m\n"
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"
145
176
  ```
146
177
 
147
- ![CleanShot 2024-07-26 at 01 46 26@2x](https://github.com/user-attachments/assets/bd1d67c6-1182-428b-be05-756f3d330f67)
178
+ Embeds can also have their own distinct pipelines.
148
179
 
149
- ### Embeds
150
-
151
- Templates can be embedded or nested within other templates... as deep as needed!
152
- Just wrap the embedded template in double curly braces: `{{EMBEDDED TEMPLATE HERE}}`
180
+ <!-- test_abb7ea -->
153
181
 
154
182
  ```ruby
155
- require "rainbow"
156
- require "fmt"
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
+ ```
157
187
 
158
- Fmt.add_rainbow_filters
188
+ Embeds can be deeply nested.
159
189
 
160
- template = "%{value}lime {{%{embed_value}red|bold|underline}}"
161
- Fmt template, value: "Outer", embed_value: "Inner"
190
+ <!-- test_79e924 -->
162
191
 
163
- #=> "\e[38;5;46mOuter\e[0m \e[31m\e[1m\e[4mInner\e[0m"
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"
164
196
  ```
165
197
 
166
- ![CleanShot 2024-07-29 at 02 42 19@2x](https://github.com/user-attachments/assets/f67dd215-b848-4a23-bd73-72822cb7d970)
198
+ Embeds can also span multiple lines.
199
+
200
+ <!-- test_054526 -->
167
201
 
168
202
  ```ruby
169
203
  template = <<~T
170
- |--%{value}yellow|bold|underline
171
- | |--{{%{inner_value}green|bold|underline
172
- | | |--{{%{deep_value}blue|bold|underline
173
- | | | |-- We're in deep!}}}}
204
+ Multiline:
205
+ %{one}|>red {{
206
+ %{two}|>blue {{
207
+ %{three}|>green
208
+ }}
209
+ }}|>bold
174
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
+ ```
175
214
 
176
- Fmt template, value: "Outer", inner_value: "Inner", deep_value: "Deep"
215
+ ### Customizing Fmt: Create Your Own Extensions
177
216
 
178
- #=> "|--\e[33m\e[1m\e[4mOuter\e[0m\n| |--\e[32m\e[1m\e[4mInner\e[0m\n| | |--\e[34m\e[1m\e[4mDeep\e[0m\n| | | |-- We're in deep!\n"
217
+ Want to add your own filters? It's easier than you might think:
218
+
219
+ <!-- test_2cacce -->
220
+
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 "
179
225
  ```
180
226
 
181
- ![CleanShot 2024-07-29 at 02 45 27@2x](https://github.com/user-attachments/assets/1b933bf4-a62d-4913-b817-d6c69b0e7028)
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!**
182
256
 
183
- ## Sponsors
257
+ ## Sponsors: Our Awesome Supporters
184
258
 
185
259
  <p align="center">
186
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