fmt 0.1.3 → 0.3.1

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +179 -86
  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 +33 -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: 67c0fef310f41def1104325bdcf64e0e74c00be1c491bde56e8dd18f93121711
4
+ data.tar.gz: fa7af34e9c94c03ebbd28d4843a0c268fe63304cd95bfde0c0b7cbca7d550916
5
5
  SHA512:
6
- metadata.gz: 1235f7508b608760ce2f984cdcb321c01ac2a1cd3d7d5b9d1b586b45614e3b79c38bf900264b569fa469c17e34d2b6e1999984715a9b51c8caa792ee7d080aa9
7
- data.tar.gz: '012095a10cbeac34f3457bf18ebe9135513a774984139cd1c7c300cd00d774590eff1826f7af137adbb32acd77f2eb8bffa1d52b48c579422fe43a2e7c3769cf'
6
+ metadata.gz: b04858bb8cab7de025a11ecf941e175a698559b231c1878bbdf9f5762dcb3eb05cd822ee50b11a0739aa21ee5077ea9a448ef75a0e790e0587c39403e33ca303
7
+ data.tar.gz: eb55994dbd819d698b626ea170b56dec4177c02742f8b50da3e1d182704c7ff78043e31aece819ec47762cf6ab4297236d0698e2d80c6b5a974e19632817bccd
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-1042-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,172 +13,265 @@
13
13
  </a>
14
14
  </p>
15
15
 
16
- # Fmt
16
+ # CLI Templating System and String Formatter
17
17
 
18
- #### A simple template engine based on native Ruby String formatting mechanics
18
+ **Fmt** is a powerful and flexible templating system and string formatter for Ruby, designed to streamline the creation of command-line interfaces and enhance general-purpose string formatting.
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)
24
+ - [Getting Started](#getting-started)
26
25
  - [Usage](#usage)
27
- - [Formatting](#formatting)
28
- - [Filters](#filters)
29
- - [Embeds](#embeds)
26
+ - [Macros](#macros)
27
+ - [Pipelines](#pipelines)
28
+ - [Supported Methods](#supported-methods)
29
+ - [Rainbow GEM](#rainbow-gem)
30
+ - [Composition](#composition)
31
+ - [Embedded Templates](#embedded-templates)
32
+ - [Customizing Fmt](#customizing-fmt)
33
+ - [Kernel Refinement](#kernel-refinement)
34
+ - [`fmt(object, *pipeline)`](#fmtobject-pipeline)
35
+ - [`fmt_print(object, *pipeline)`](#fmt_printobject-pipeline)
36
+ - [`fmt_puts(object, *pipeline)`](#fmt_putsobject-pipeline)
37
+ - [Performance](#performance)
30
38
  - [Sponsors](#sponsors)
31
39
 
32
40
  <!-- Tocer[finish]: Auto-generated, don't remove. -->
33
41
 
34
- ## Why?
42
+ ## Getting Started
35
43
 
36
- I'm currenly using this to help build beautiful CLI applications with Ruby. Plus it's fun.
44
+ Install the required dependencies:
37
45
 
38
- ## Setup
39
-
40
- ```
41
- bundle add rainbow # <- optional
46
+ ```sh
47
+ bundle add rainbow # <- optional, for color support
42
48
  bundle add fmt
43
49
  ```
44
50
 
51
+ Then, require the necessary libraries in your Ruby file:
52
+
53
+ ```ruby
54
+ require "rainbow" # <- optional, for color support
55
+ require "fmt"
56
+ ```
57
+
45
58
  ## Usage
46
59
 
47
- Simply create a string with embedded formatting syntax as you'd normally do with `sprintf` or `format`.
60
+ Fmt uses Ruby's native [format specifiers](https://ruby-doc.org/3.3.5/format_specifications_rdoc.html) to create templates:
61
+
62
+ - `"%s"` - Standard format specifier
63
+ - `"%{variable}"` - Named format specifier
64
+ - `"%<variable>s"` - Named format specifier _(alternative syntax)_
65
+
66
+ ### Macros
67
+
68
+ Formatting macros are appended to format specifiers to modify the output:
69
+
70
+ <!-- test_e798c3 -->
48
71
 
49
72
  ```ruby
50
- "%{...}"
73
+ Fmt("%s|>capitalize", "hello world!") # => "Hello world!"
74
+ Fmt("%{msg}|>capitalize", msg: "hello world!") # => "Hello world!"
51
75
  ```
52
76
 
53
- Filters can be chained after the placeholder like so.
77
+ Macros can accept arguments:
78
+
79
+ <!-- test_1707d2 -->
54
80
 
55
81
  ```ruby
56
- "%{...}FILTER|FILTER|FILTER"
82
+ Fmt("%s|>prepend('Hello ')", "world!") # => "Hello world!"
83
+ Fmt("%{msg}|>prepend('Hello ')", msg: "world!") # => "Hello world!"
57
84
  ```
58
85
 
59
- > [!NOTE]
60
- > Filters are processed in the order they are specified.
86
+ ### Pipelines
61
87
 
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.
88
+ Macros can be chained to create a formatting pipeline:
89
+
90
+ <!-- test_425625 -->
91
+
92
+ ```ruby
93
+ Fmt("%s|>prepend('Hello ')|>ljust(32, '.')|>upcase", "world!") # => "HELLO WORLD!...................."
94
+ Fmt("%{msg}|>prepend('Hello ')|>ljust(32, '.')|>upcase", msg: "world!") # => "HELLO WORLD!...................."
95
+ ```
64
96
 
65
- **You can even [register your own filters](#filters).**
97
+ Pipelines are processed left to right, with the return value from the preceding macro serving as the starting value for the next macro.
66
98
 
67
- ### Formatting
99
+ Arguments and return values can be of any type:
68
100
 
69
- Basic example:
101
+ <!-- test_f55ae2 -->
70
102
 
71
103
  ```ruby
72
- require "rainbow"
73
- require "fmt"
104
+ Fmt("%p|>partition(/:/)|>last|>delete_suffix('>')", Object.new) # => "0x000000011f33bc68"
105
+ ```
106
+
107
+ ### Supported Methods
108
+
109
+ Most public instance methods on the following classes are supported:
74
110
 
75
- Fmt.add_rainbow_filters
111
+ `Array`, `Date`, `DateTime`, `FalseClass`, `Float`, `Hash`, `Integer`, `NilClass`, `Range`, `Regexp`, `Set`, `StandardError`, `String`, `Struct`, `Symbol`, `Time`, `TrueClass`
76
112
 
77
- template = "Hello %{name}cyan|bold"
78
- Fmt template, name: "World"
113
+ Extension methods from libraries like ActiveSupport will also be available if the library is required before Fmt.
79
114
 
80
- #=> "Hello \e[36m\e[1mWorld\e[0m"
115
+ #### Rainbow GEM
116
+
117
+ Color and style support is available if your project includes the [Rainbow GEM](https://github.com/ku1ik/rainbow):
118
+
119
+ <!-- test_19c8ca -->
120
+
121
+ ```ruby
122
+ template = "%{msg}|>cyan|>bold|>underline"
123
+ Fmt(template, msg: "Hello World!")
124
+ #=> "\e[36m\e[1m\e[4mHello World!\e[0m"
81
125
  ```
82
126
 
83
- ![CleanShot 2024-07-26 at 01 40 33@2x](https://github.com/user-attachments/assets/04ff90e6-254a-42d4-9169-586ac24b82f0)
127
+ ### Composition
84
128
 
85
- Mix and match native formatting with Rainbow formatting:
129
+ Templates can include multiple format strings with distinct pipelines:
130
+
131
+ <!-- test_0dbfcd -->
86
132
 
87
133
  ```ruby
88
- require "rainbow"
89
- require "fmt"
134
+ template = "Date: %<date>.10s|>magenta -- %{msg}|>titleize|>bold"
135
+ Fmt(template, date: Time.now, msg: "this is cool")
136
+ #=> "Date: \e[35m2024-09-20\e[0m \e[1mThis Is Cool\e[0m"
137
+ ```
138
+
139
+ #### Embedded Templates
90
140
 
91
- Fmt.add_rainbow_filters
141
+ Embedded templates can be nested within other templates:
92
142
 
93
- template = "Date: %{date}.10s|magenta"
94
- Fmt template, date: Time.now
143
+ <!-- test_efee7a -->
95
144
 
96
- #=> "Date: \e[35m2024-07-26\e[0m"
145
+ ```ruby
146
+ template = "%{msg}|>faint {{%{embed}|>bold}}"
147
+ Fmt(template, msg: "Look Ma...", embed: "I'm embedded!")
148
+ #=> "\e[2mLook Ma...\e[0m \e[1mI'm embedded!\e[0m"
97
149
  ```
98
150
 
99
- ![CleanShot 2024-07-26 at 01 42 53@2x](https://github.com/user-attachments/assets/507913b0-826b-4526-9c79-27f766c904b3)
151
+ Embeds can have their own pipelines:
100
152
 
101
- Multiline example:
153
+ <!-- test_abb7ea -->
102
154
 
103
155
  ```ruby
104
- require "rainbow"
105
- require "fmt"
156
+ template = "%{msg}|>faint {{%{embed}|>bold}}|>underline"
157
+ Fmt(template, msg: "Look Ma...", embed: "I'm embedded!")
158
+ #=> "\e[2mLook Ma...\e[0m \e[1m\e[4mI'm embedded!\e[0m"
159
+ ```
106
160
 
107
- Fmt.add_rainbow_filters
161
+ Embeds can be deeply nested:
108
162
 
109
- template = <<~T
110
- Date: %{date}.10s|underline
163
+ <!-- test_79e924 -->
111
164
 
112
- Greetings, %{name}upcase|bold
165
+ ```ruby
166
+ template = "%{msg}|>faint {{%{embed}|>bold {{%{deep_embed}|>red|>bold}}}}"
167
+ Fmt(template, msg: "Look Ma...", embed: "I'm embedded!", deep_embed: "And I'm deeply embedded!")
168
+ #=> "\e[2mLook Ma...\e[0m \e[1mI'm embedded!\e[0m \e[31m\e[1mAnd I'm deeply embedded!\e[0m"
169
+ ```
113
170
 
114
- %{message}strip|green
115
- T
171
+ Embeds can also span multiple lines:
116
172
 
117
- Fmt template, date: Time.now, name: "Hopsoft", message: "This is neat!"
173
+ <!-- test_054526 -->
118
174
 
119
- #=> "Date: \e[4m2024-07-26\e[0m\n\nGreetings, \e[1mHOPSOFT\e[0m\n\n\e[32mThis is neat!\e[0m\n"
175
+ ```ruby
176
+ template = <<~T
177
+ Multiline:
178
+ %{one}|>red {{
179
+ %{two}|>blue {{
180
+ %{three}|>green
181
+ }}
182
+ }}|>bold
183
+ T
184
+ Fmt(template, one: "Red", two: "Blue", three: "Green")
185
+ #=> "Multiline:\n\e[31mRed\e[0m \e[1m\n \e[34mBlue\e[0m \n \e[32mGreen\e[0m"
120
186
  ```
121
187
 
122
- ![CleanShot 2024-07-26 at 01 44 30@2x](https://github.com/user-attachments/assets/8926009c-7cf1-4140-9a2a-6ed718d50926)
188
+ ### Customizing Fmt
123
189
 
124
- ### Filters
190
+ Add custom filters by registering them with Fmt:
125
191
 
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.
192
+ <!-- test_2cacce -->
128
193
 
129
194
  ```ruby
130
- require "rainbow"
131
- require "fmt"
195
+ Fmt.register([Object, :shuffle]) { |*args, **kwargs| to_s.chars.shuffle.join }
196
+ Fmt("%s|>shuffle", "This don't make no sense.")
197
+ #=> "de.nnoTtsnh'oeek ssim a "
198
+ ```
132
199
 
133
- Fmt.add_rainbow_filters
134
- Fmt.add_filter(:ljust) { |val| "".ljust 14, val.to_s }
200
+ Run a Ruby block with temporary filters without officially registering them:
135
201
 
136
- template = <<~T
137
- %{head}ljust|faint
138
- %{message}bold
139
- %{tail}ljust|faint
140
- T
202
+ <!-- test_7df4eb -->
141
203
 
142
- Fmt template, head: "#", message: "Give it a try!", tail: "#"
204
+ ```ruby
205
+ Fmt.with_overrides([Object, :red] => proc { |*args, **kwargs| Rainbow(self).crimson.bold }) do
206
+ Fmt("%s|>red", "This is customized red!")
207
+ #=> "\e[38;5;197m\e[1mThis is customized red!\e[0m"
208
+ end
143
209
 
144
- #=> "\e[2m##############\e[0m\n\e[1mGive it a try!\e[0m\n\e[2m##############\e[0m\n"
210
+ Fmt("%s|>red", "This is original red!")
211
+ #=> "\e[31mThis is original red!\e[0m"
145
212
  ```
146
213
 
147
- ![CleanShot 2024-07-26 at 01 46 26@2x](https://github.com/user-attachments/assets/bd1d67c6-1182-428b-be05-756f3d330f67)
214
+ ## Kernel Refinement
215
+
216
+ Fmt provides a kernel refinement that adds convenient methods for formatting and outputting text directly. To use these methods, you need to enable the refinement in your code:
217
+
218
+ ```ruby
219
+ using Fmt::KernelRefinement
220
+ ```
148
221
 
149
- ### Embeds
222
+ Once enabled, you'll have access to the following methods:
150
223
 
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}}`
224
+ ### `fmt(object, *pipeline)`
225
+
226
+ This method formats an object using a different pipeline syntax:
153
227
 
154
228
  ```ruby
155
- require "rainbow"
156
- require "fmt"
229
+ fmt("Hello, World!", :bold) # => "\e[1mHello, World!\e[0m"
230
+ fmt(:hello, :underline) # => "\e[4mhello\e[0m"
231
+ fmt(Object.new, :red) # => "\e[31m#<Object:0x00007f9b8b0b0a08>\e[0m"
232
+ ```
157
233
 
158
- Fmt.add_rainbow_filters
234
+ ### `fmt_print(object, *pipeline)`
159
235
 
160
- template = "%{value}lime {{%{embed_value}red|bold|underline}}"
161
- Fmt template, value: "Outer", embed_value: "Inner"
236
+ This method formats an object and prints it to STDOUT without a newline:
162
237
 
163
- #=> "\e[38;5;46mOuter\e[0m \e[31m\e[1m\e[4mInner\e[0m"
238
+ ```ruby
239
+ fmt_print("Hello, World!", :italic) # Prints: "\e[3mHello, World!\e[0m"
240
+ fmt_print(:hello, :green) # Prints: "\e[32mhello\e[0m"
164
241
  ```
165
242
 
166
- ![CleanShot 2024-07-29 at 02 42 19@2x](https://github.com/user-attachments/assets/f67dd215-b848-4a23-bd73-72822cb7d970)
243
+ ### `fmt_puts(object, *pipeline)`
244
+
245
+ This method formats an object and prints it to STDOUT with a newline:
167
246
 
168
247
  ```ruby
169
- template = <<~T
170
- |--%{value}yellow|bold|underline
171
- | |--{{%{inner_value}green|bold|underline
172
- | | |--{{%{deep_value}blue|bold|underline
173
- | | | |-- We're in deep!}}}}
174
- T
248
+ fmt_puts("Hello, World!", :bold, :underline) # Prints: "\e[1m\e[4mHello, World!\e[0m\n"
249
+ fmt_puts(:hello, :magenta) # Prints: "\e[35mhello\e[0m\n"
250
+ ```
175
251
 
176
- Fmt template, value: "Outer", inner_value: "Inner", deep_value: "Deep"
252
+ These methods provide a convenient way to use Fmt's formatting capabilities directly in your code without explicitly calling the `Fmt` method.
177
253
 
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"
254
+ You can pass any number of macros when using these methods:
255
+
256
+ ```ruby
257
+ fmt("Important!", :red, :bold, :underline)
258
+ # => "\e[31m\e[1m\e[4mImportant!\e[0m"
259
+
260
+ fmt_puts("Warning:", :yellow, :italic)
261
+ # Prints: "\e[33m\e[3mWarning:\e[0m\n"
179
262
  ```
180
263
 
181
- ![CleanShot 2024-07-29 at 02 45 27@2x](https://github.com/user-attachments/assets/1b933bf4-a62d-4913-b817-d6c69b0e7028)
264
+ These kernel methods make it easy to integrate Fmt's powerful formatting capabilities into your command-line interfaces or any part of your Ruby application where you need to format and output text.
265
+
266
+ ## Performance
267
+
268
+ Fmt is optimized for performance:
269
+
270
+ - Tokenization: Uses StringScanner and Ripper to parse and tokenize templates
271
+ - Caching: Stores an Abstract Syntax Tree (AST) representation of each template, pipeline, and macro
272
+ - Speed: Current benchmarks show an average pipeline execution time of under 0.3 milliseconds
273
+
274
+ Complex pipelines may take slightly longer to execute.
182
275
 
183
276
  ## Sponsors
184
277
 
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