supports_pointer 0.5.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/MIT-LICENSE +20 -0
- data/README.md +94 -0
- data/Rakefile +3 -0
- data/lib/core_ext/regexp.rb +340 -0
- data/lib/core_ext/string.rb +46 -0
- data/lib/supports_pointer/railtie.rb +4 -0
- data/lib/supports_pointer/version.rb +3 -0
- data/lib/supports_pointer.rb +239 -0
- data/lib/tasks/supports_pointer_tasks.rake +4 -0
- data/lib/terminal/terminal.rb +166 -0
- data/lib/terminal/terminal_formatting_support.rb +6 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3e4e9bf143c824bb44ee0d6118ce2b19e931250728e6e7c656297f4cc651e304
|
4
|
+
data.tar.gz: 6604b4d85a25ce1b2467c85f7bf797a6ecb84d59c4cb06a9b7848893dc458c6a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90bf68d4e41547e0dcf6a6fd92fa5cfbb28b2853e8a8efb1f88d91dc86cf4a7267d1510ac839b7af701fcdfff441c045a9e051cbc0fe1f470f35882b9045da6c
|
7
|
+
data.tar.gz: bfceb10d6c0234f3faa1cc2116d834e59f2567fccf938c10ec6d4a13da6360ee5bd845af63ced96166dc76ef6c041e7bbcf6d365e371350421e91db58c8cbf2f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022 Greg Mikeska
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# SupportsPointer
|
2
|
+
The SupportsPointer gem loads a concern to allow support for parsing and generation of various pointer types
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
Add the following to your model:
|
6
|
+
```ruby
|
7
|
+
parses_pointer :pointer_name_as_symbol,
|
8
|
+
template:/regexp(?<with_captures>.*)for(?<each_field>)/,
|
9
|
+
resolve:{|data| data[:with_captures].find_by(some_param:data[:other_capture]),
|
10
|
+
generate:{|target| "#{target.some_attr}:#{target.other_attr}"}
|
11
|
+
```
|
12
|
+
|
13
|
+
if the pointer being declared is a model pointer, or model_instance pointer, simply declare:
|
14
|
+
```ruby
|
15
|
+
parses_pointer :model
|
16
|
+
# and/or
|
17
|
+
parses_pointer :model_instance
|
18
|
+
```
|
19
|
+
if you use a model or model instance pointer that requires special handling (ie:alternate index param)
|
20
|
+
you can overwrite the default resolver or generator with
|
21
|
+
```ruby
|
22
|
+
pointer_resolution {|data| some_block_to_resolve_pointer}
|
23
|
+
# or
|
24
|
+
pointer_generation {|data| some_block_to_generate_pointer}
|
25
|
+
```
|
26
|
+
|
27
|
+
Pointers are inherited like any other method. For instance, model & model instance
|
28
|
+
can be declared on ApplicationRecord to allow parsing from any model in the project.
|
29
|
+
However, pointers can also be parsed by
|
30
|
+
other classes where the declaration is outside the class hierarchy.
|
31
|
+
Lets say you have a "handle" pointer defined directly on class User,
|
32
|
+
of the format "@username"
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
parses_pointer :handle, template:/\^(?<handle>\w*)/, resolve:{|data| User.find_by handle:data[:handle]}
|
36
|
+
pointer_generation :handle do |data| # feel free to mix-and-match between single-statement declarations
|
37
|
+
"@#{data.handle}" # and using pointer_resolution or pointer_generation methods.
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
In the above example only the "User" model will be able to generate, parse or resolve
|
42
|
+
"handle" pointers. To allow another model (say, "Widget", for example) to resolve user handles
|
43
|
+
you'd add the following to class Widget:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
uses_pointer :handle, from:User
|
47
|
+
```
|
48
|
+
|
49
|
+
This will allow the Widget class to access the handle pointer.
|
50
|
+
|
51
|
+
A pointer can be parsed by calling ```parse_pointer``` on any model or object
|
52
|
+
which supports the pointer type in question. In situations where a string matches
|
53
|
+
the regexp of multiple pointer types, you can specify the pointer_type used for parsing with ```parse_{handle_name}_pointer``` such as ```parse_model_pointer``` or ```parse_model_instance_pointer```.
|
54
|
+
|
55
|
+
When declaring model & model instance pointers, it may be helpful to declare a ```to_pointer``` method, returning ```generate_model_pointer``` and ```generate_model_instance_pointer```
|
56
|
+
respectively:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
def self.to_pointer
|
60
|
+
return generate_model_pointer(self)
|
61
|
+
end
|
62
|
+
def to_pointer
|
63
|
+
return generate_model_instance_pointer(self)
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
For more information, see the BlogPost and User models in ```/spec/dummy/models```.
|
68
|
+
Note that while the dummy app contains a model called SettingsModel, this is being
|
69
|
+
used to develop a pointer methodology to reference data inside a model's hash attributes.
|
70
|
+
Documentation will be updated when the methodology is complete.
|
71
|
+
|
72
|
+
|
73
|
+
## Installation
|
74
|
+
Add this line to your application's Gemfile:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
gem "supports_pointer"
|
78
|
+
```
|
79
|
+
|
80
|
+
And then execute:
|
81
|
+
```bash
|
82
|
+
$ bundle
|
83
|
+
```
|
84
|
+
|
85
|
+
Or install it yourself as:
|
86
|
+
```bash
|
87
|
+
$ gem install supports_pointer
|
88
|
+
```
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
Feel free to fork the repo. Pull requests are welcome for features and bug-fixes!
|
92
|
+
|
93
|
+
## License
|
94
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,340 @@
|
|
1
|
+
require_relative "../terminal/terminal_formatting_support"
|
2
|
+
|
3
|
+
class Regexp
|
4
|
+
def append(other_regex)
|
5
|
+
if(other_regex.is_a? Regexp)
|
6
|
+
other_string = other_regex.source
|
7
|
+
else
|
8
|
+
other_string = other_regex.to_s
|
9
|
+
end
|
10
|
+
Regexp.new(self.source+other_string)
|
11
|
+
end
|
12
|
+
def prepend(other_regex)
|
13
|
+
if(other_regex.is_a? Regexp)
|
14
|
+
other_string = other_regex.source
|
15
|
+
else
|
16
|
+
other_string = other_regex.to_s
|
17
|
+
end
|
18
|
+
Regexp.new(other_string+self.source)
|
19
|
+
end
|
20
|
+
def template
|
21
|
+
if(!@template)
|
22
|
+
@template = Regexp::Template.new(source:self.source)
|
23
|
+
end
|
24
|
+
return @template
|
25
|
+
end
|
26
|
+
class Template
|
27
|
+
include TerminalFormattingSupport
|
28
|
+
MATCHERS = {
|
29
|
+
any:".",
|
30
|
+
alpha:"[a-zA-Z]",
|
31
|
+
numeric:"\\d",
|
32
|
+
line_end:"$",
|
33
|
+
line_start:"^",
|
34
|
+
nonnumeric:"\\D",
|
35
|
+
whitespace:"\\s",
|
36
|
+
alphanumeric:"\\w",
|
37
|
+
nonwhitespace:"\\S",
|
38
|
+
nonalphanumeric:"\\W"
|
39
|
+
}
|
40
|
+
TYPE_MATCHER_DEFAULTS = {
|
41
|
+
string:".*",
|
42
|
+
integer:"\\d*",
|
43
|
+
decimal:"(\\d*.?\\d*)",
|
44
|
+
}
|
45
|
+
attr_reader :rx, :atoms
|
46
|
+
def self.capture_group(atoms,**args)
|
47
|
+
return Regexp::Template.new(atoms:[:placeholder_template]).capture_group(atoms,**args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(**args)
|
51
|
+
if(args.keys.length == 0)
|
52
|
+
@rx = Regexp.new("")
|
53
|
+
elsif(args[:source_rx].is_a?(String))
|
54
|
+
@rx = Regexp.new(args[:source_rx])
|
55
|
+
elsif(args[:source_rx].is_a?(Regexp))
|
56
|
+
@rx = args[:source_rx]
|
57
|
+
elsif(!!args[:atoms] && args[:atoms].is_a?(Array))
|
58
|
+
@atoms = args[:atoms]
|
59
|
+
set_regex_from_atoms
|
60
|
+
end
|
61
|
+
@type_matchers = {}.merge(Template::TYPE_MATCHER_DEFAULTS)
|
62
|
+
|
63
|
+
if(!@atoms)
|
64
|
+
self.analyze
|
65
|
+
end
|
66
|
+
end
|
67
|
+
def set_regex_from_atoms
|
68
|
+
@rx = Regexp.new(@atoms.map{|a| render_atom(a) }.join(""))
|
69
|
+
end
|
70
|
+
def push(o)
|
71
|
+
if(o.is_a? Regexp)
|
72
|
+
o = o.source
|
73
|
+
elsif(o.is_a? Regexp::Template)
|
74
|
+
o = o.rx.source
|
75
|
+
end
|
76
|
+
@atoms << o
|
77
|
+
set_regex_from_atoms
|
78
|
+
end
|
79
|
+
def <<(o)
|
80
|
+
self.push(o)
|
81
|
+
end
|
82
|
+
def types
|
83
|
+
return @type_matchers.keys
|
84
|
+
end
|
85
|
+
def add_type(type_name, matcher)
|
86
|
+
@type_matchers[type_name.to_sym] = matcher
|
87
|
+
end
|
88
|
+
def preview(parent_type=nil)
|
89
|
+
numbering = @atoms.map.with_index do |a, index|
|
90
|
+
atom_text = render_atom(a)
|
91
|
+
# source = @rx.source.gsub(atom_text, atom_text+"|")
|
92
|
+
# spacer_count = (atom_text+"|").length/2
|
93
|
+
spacer_count = (atom_text).length/2
|
94
|
+
"#{" "*(spacer_count-1)}##{index}#{" "*(spacer_count)}"
|
95
|
+
end
|
96
|
+
@terminal = Terminal.new()
|
97
|
+
@terminal.clear_terminal
|
98
|
+
rx_source = @rx.source
|
99
|
+
descriptions = []
|
100
|
+
if(!parent_type)
|
101
|
+
@atoms.map.with_index do |a,index|
|
102
|
+
if(a.is_a?(Array) && a[0] == :"(" && a[a.length-1] == :")")
|
103
|
+
stringified = a.map(&:to_s).join('')
|
104
|
+
rx_source = rx_source.gsub(stringified,@terminal.rbg(r:0,b:0,g:5, text:stringified))
|
105
|
+
subatoms = a[1,a.length-2]
|
106
|
+
if(subatoms[0].to_s.start_with?("?<"))
|
107
|
+
name = subatoms.shift
|
108
|
+
name = name.to_s.match(/\?\<(?<name>.*)\>/)[:name]
|
109
|
+
name = "#{name}"
|
110
|
+
else
|
111
|
+
name = "anonymous_capture_group"
|
112
|
+
end
|
113
|
+
if(subatoms.length == 1)
|
114
|
+
a = "#{@terminal.rbg(r:0,b:0,g:5, text:"Capture group \"#{name}\"")} captures #{self.describe(subatoms.first)}"
|
115
|
+
elsif(subatoms.length == 2)
|
116
|
+
a = "#{@terminal.rbg(r:0,b:0,g:5, text:"Capture group \"#{name}\"")} captures #{self.describe(subatoms[0])} and #{self.describe(subatoms[0])}"
|
117
|
+
else
|
118
|
+
subatoms = subatoms.map{|s| self.describe(s)}
|
119
|
+
last_subatom = subatoms.pop
|
120
|
+
a = "#{@terminal.rbg(r:0,b:0,g:5, text:"Capture group \"#{name}\"")} captures #{subatoms.join(',')} and #{last_subatom}"
|
121
|
+
end
|
122
|
+
else
|
123
|
+
rx_source = rx_source.gsub(a.to_s,@terminal.rbg(r:0,b:5,g:1, text:a.to_s))
|
124
|
+
a = "#{@terminal.rbg(r:0,b:5,g:1, text:"\"#{a}\"")} matches #{self.describe(a)}"
|
125
|
+
end
|
126
|
+
descriptions << "##{index}. #{a}"
|
127
|
+
end
|
128
|
+
elsif(parent_type == :capture)
|
129
|
+
@atoms.map do |a|
|
130
|
+
if(a.to_s.include?("literal_"))
|
131
|
+
"#{a.to_s.gsub("_"," ")}"
|
132
|
+
elsif(a.to_s[0] == "(" && a.to_s[a.length-1] == ")")
|
133
|
+
"#{self.preview(a.to_s[1,a.length-1],:capture)}"
|
134
|
+
elsif(a.to_s[0] == "\\" && matchers.keys.include?(a.to_s[1]))
|
135
|
+
"#{matchers[a]}"
|
136
|
+
elsif(a.to_s[0] == "\\" && a.length <= 3 && !matchers.keys.include?(a.to_s[1]))
|
137
|
+
"literal #{a}"
|
138
|
+
else
|
139
|
+
"#{a}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
indent = 1
|
144
|
+
indent_str = "\t"*indent
|
145
|
+
puts "\n"*2
|
146
|
+
puts "#{indent_str}#{rx_source}"
|
147
|
+
puts "#{indent_str}#{@terminal.add_formatting(numbering.join(''), :yellow)}"
|
148
|
+
descriptions.each{|desc| puts "#{indent_str}#{desc}" }
|
149
|
+
return
|
150
|
+
end
|
151
|
+
def describe(atom)
|
152
|
+
matchers = {}
|
153
|
+
match_count = "exactly 1 instance"
|
154
|
+
if(atom.to_s[atom.to_s.length-1] == "*")
|
155
|
+
match_count = "any number of"
|
156
|
+
elsif(atom.to_s[atom.to_s.length-1] == "?")
|
157
|
+
match_count = "zero or one"
|
158
|
+
elsif(atom.to_s[atom.to_s.length-1] == "+")
|
159
|
+
match_count = "1 or more"
|
160
|
+
elsif(atom.to_s.match(/\{(?<count>.*)\}\z/))
|
161
|
+
match_count_data = atom.to_s.match(/\{(?<count>.*)\}\z/)[:count]
|
162
|
+
if(match_count_data.split(',').length == 2)
|
163
|
+
match_count = "between #{match_count_data.split(',').first} and #{match_count_data.split(',').last}"
|
164
|
+
elsif(match_count_data > 1)
|
165
|
+
match_count = "exactly #{match_count_data}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
if(atom.to_s.include?("literal_"))
|
170
|
+
desc = "#{atom.to_s.gsub("_"," ")}"
|
171
|
+
color= :yellow
|
172
|
+
elsif(atom.to_s[0] == "(" && atom.to_s[atom.length-1] == ")")
|
173
|
+
desc = "#{self.preview(atom.to_s[1,atom.length-1],:capture)}"
|
174
|
+
elsif(atom.to_s[0] == "\\" && self.class::MATCHERS.keys.include?(atom.to_s[1]))
|
175
|
+
desc = "#{self.class::MATCHERS[atom.to_s[1]]} characters"
|
176
|
+
color= :yellow
|
177
|
+
elsif(@type_matchers.values.include?(atom.to_s))
|
178
|
+
matcher_name = (@type_matchers.select{|k| @type_matchers[k] == atom.to_s}.first).first.to_s
|
179
|
+
desc = "#{matcher_name}"
|
180
|
+
type_matcher = true
|
181
|
+
color = {r:4,b:0,g:4}
|
182
|
+
elsif(atom.to_s[0] == "\\" && atom.length <= 3 && !self.class::MATCHERS.keys.include?(atom.to_s[1]))
|
183
|
+
desc = "literal #{atom}"
|
184
|
+
color= :yellow
|
185
|
+
else
|
186
|
+
desc = @terminal.rbg(r:0,b:5,g:0, text:"\"#{atom}\"")
|
187
|
+
end
|
188
|
+
if(!type_matcher)
|
189
|
+
desc = "#{match_count} of #{@terminal.add_formatting(desc, color)}"
|
190
|
+
return desc.gsub(" of of "," of ")
|
191
|
+
else
|
192
|
+
desc[0] = desc[0].upcase
|
193
|
+
if("aeiou".include?(desc[0,1].downcase))
|
194
|
+
article = "an"
|
195
|
+
else
|
196
|
+
article = "a"
|
197
|
+
end
|
198
|
+
if(!!color && color.is_a?(Hash))
|
199
|
+
color[:text] = desc
|
200
|
+
desc = "#{article} #{@terminal.rbg(**color)}"
|
201
|
+
elsif(!!color)
|
202
|
+
desc = "#{article} #{@terminal.add_formatting(desc, color)}"
|
203
|
+
else
|
204
|
+
desc = "#{article} #{desc}."
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
def analyze
|
209
|
+
if(!!@rx && !!@rx.source)
|
210
|
+
src = @rx.source
|
211
|
+
end
|
212
|
+
|
213
|
+
current_segment = ""
|
214
|
+
current_atom = {}
|
215
|
+
atom_complete = false
|
216
|
+
while(!atom_complete)
|
217
|
+
src.split('').each do |char|
|
218
|
+
current_segment = "#{current_segment}#{char}"
|
219
|
+
if(char == "\\")
|
220
|
+
current_atom[:escaped] = true
|
221
|
+
end
|
222
|
+
if(char == ".")
|
223
|
+
current_atom[:match] = :any
|
224
|
+
end
|
225
|
+
if(char == "*")
|
226
|
+
current_atom[:count] = :any
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def get_atom(**args)
|
233
|
+
self.class.get_atom(**args)
|
234
|
+
end
|
235
|
+
|
236
|
+
def self.get_atom(**args) # args[:match] ":any, :numeric, :alphanumeric, etc, also ex: :except_numeric"
|
237
|
+
# args[:count] see self.count
|
238
|
+
# args[:string] for 'literal' text to be matched
|
239
|
+
if(!args[:types])
|
240
|
+
args[:types] = TYPE_MATCHER_DEFAULTS
|
241
|
+
else
|
242
|
+
args[:types] = args[:types]
|
243
|
+
end
|
244
|
+
|
245
|
+
result = ""
|
246
|
+
if(!!args[:match])
|
247
|
+
|
248
|
+
if(args[:match].to_s.include?('except_'))
|
249
|
+
args[:match] = args[:match].split("_")[1]
|
250
|
+
except = true
|
251
|
+
else
|
252
|
+
except = false
|
253
|
+
end
|
254
|
+
if(MATCHERS.keys.include?(args[:match]))
|
255
|
+
result = MATCHERS[args[:match].to_sym]
|
256
|
+
if(!!args[:count])
|
257
|
+
result = "#{result}#{self.count(args[:count])}"
|
258
|
+
end
|
259
|
+
elsif(args[:types].keys.include?(args[:match]))
|
260
|
+
result = "#{args[:types][args[:match].to_sym].to_s}"
|
261
|
+
end
|
262
|
+
elsif(!!args[:string])
|
263
|
+
if(args[:string].length == 2 && args[:string] == Regexp.escape(args[:string][1]))
|
264
|
+
result = "literal_#{args[:string][1]}"
|
265
|
+
else
|
266
|
+
result = args[:string]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
if(!!except)
|
271
|
+
result = "[^#{result}]"
|
272
|
+
end
|
273
|
+
return result.to_sym
|
274
|
+
end
|
275
|
+
|
276
|
+
def render_atom(atom)
|
277
|
+
if(atom.is_a? Array)
|
278
|
+
atom = atom.map(&:to_s).join('')
|
279
|
+
end
|
280
|
+
atom_str = atom.to_s
|
281
|
+
if(atom_str.include?("literal_"))
|
282
|
+
return self.escape(atom_str.split("_").last)
|
283
|
+
else
|
284
|
+
return atom_str
|
285
|
+
end
|
286
|
+
end
|
287
|
+
def literal(atom_string)
|
288
|
+
return "#{self.escape(atom_string.to_s)}"
|
289
|
+
end
|
290
|
+
def line_start
|
291
|
+
return "^"
|
292
|
+
end
|
293
|
+
def line_end
|
294
|
+
return "$"
|
295
|
+
end
|
296
|
+
def count(count)
|
297
|
+
self.class.count(count)
|
298
|
+
end
|
299
|
+
def self.count(count)
|
300
|
+
if(count == :any)
|
301
|
+
return "*"
|
302
|
+
end
|
303
|
+
# if(count.is_a? Symbol)
|
304
|
+
if(count.to_s.match(/\>(?<count>\d*)$/))
|
305
|
+
count_number = count.to_s.match(/\>(?<count>\d*)/)[:count].to_i
|
306
|
+
return "+" if(count_number == 0)
|
307
|
+
return "{#{count_number},}"
|
308
|
+
elsif(count.to_s.match(/0_or_1/))
|
309
|
+
return "?"
|
310
|
+
elsif(count.to_s.match(/\>(?<count_min>\d*)_\<(?<count_max>\d*)/)) # Example: count(">3_<5".to_sym) for "greater than 3 less than 5"
|
311
|
+
counts = count.match(/\>(?<count_min>\d*)_\<(?<count_max>\d*)/)
|
312
|
+
count_min = counts[:count_min].to_i
|
313
|
+
count_max = counts[:count_max].to_i
|
314
|
+
return "{#{count_min},#{count_max}}"
|
315
|
+
elsif(count.to_s.match(/(?<count>\d*)/))
|
316
|
+
return"{#{count.to_s.match(/(?<count>\d*)/)[:count].to_i}}"
|
317
|
+
end
|
318
|
+
end
|
319
|
+
def capture_group(atoms,**args)
|
320
|
+
if(!atoms.is_a?(Array))
|
321
|
+
atoms = [atoms]
|
322
|
+
end
|
323
|
+
if(args[:name])
|
324
|
+
atoms.unshift("?<#{args[:name]}>".to_sym)
|
325
|
+
end
|
326
|
+
atoms.unshift(:"(")
|
327
|
+
atoms.push(:")")
|
328
|
+
def atoms.to_sym
|
329
|
+
self.map{|a| render_atom(a) }.join('').to_sym
|
330
|
+
end
|
331
|
+
return atoms
|
332
|
+
end
|
333
|
+
def self.group(atoms)
|
334
|
+
return "[#{atoms.join("")}]"
|
335
|
+
end
|
336
|
+
def group(atoms)
|
337
|
+
return "[#{atoms.join("")}]"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class String
|
2
|
+
def select(**args)
|
3
|
+
args[:parent] = self
|
4
|
+
Selection.new(**args)
|
5
|
+
end
|
6
|
+
|
7
|
+
class Selection
|
8
|
+
attr_accessor :parent, :cursor, :length, :value
|
9
|
+
def initialize(**args)
|
10
|
+
@parent = args[:parent]
|
11
|
+
@cursor = args[:cursor].to_i
|
12
|
+
@value = @parent[@cursor,args[:length]]
|
13
|
+
end
|
14
|
+
|
15
|
+
def length
|
16
|
+
@value.length
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
return source
|
21
|
+
end
|
22
|
+
|
23
|
+
def source
|
24
|
+
@parent[self.cursor,self.length]
|
25
|
+
end
|
26
|
+
|
27
|
+
def source=(replacement)
|
28
|
+
@parent[self.cursor,self.length] = replacement
|
29
|
+
@value = @parent[self.cursor,replacement.length]
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def method_missing(m,*args, &block)
|
34
|
+
if(!args)
|
35
|
+
args = []
|
36
|
+
end
|
37
|
+
@value.send(m,*args,&block) # preplay action on @value to maintain length
|
38
|
+
if(!!args[0] && args[0].is_a?(Integer))
|
39
|
+
args[0] = @cursor+args[0]
|
40
|
+
@parent.send(m,*args,&block)
|
41
|
+
else
|
42
|
+
source.send(m,*args,&block)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require "core_ext/regexp"
|
2
|
+
require "core_ext/string"
|
3
|
+
require "supports_pointer/version"
|
4
|
+
require "supports_pointer/railtie"
|
5
|
+
|
6
|
+
module SupportsPointer
|
7
|
+
MODEL_PARSER_ATOMS = [:"(?<model_name>\\w*)"]
|
8
|
+
MODEL_RESOLVER = Proc.new{ |data| data[:model_name].classify.constantize }
|
9
|
+
MODEL_GENERATOR = Proc.new{ |data| data.name }
|
10
|
+
|
11
|
+
MODEL_INSTANCE_PARSER_ATOMS = [:"(?<model_name>\\w*):(?<param>\\w*)"]
|
12
|
+
MODEL_INSTANCE_RESOLVER = Proc.new{ |data| data[:model_name].classify.constantize.find(data[:param]) }
|
13
|
+
MODEL_INSTANCE_GENERATOR = Proc.new{ |data| "#{data.class.name}:#{data.to_param}" }
|
14
|
+
|
15
|
+
HANDLE_PARSER_ATOMS = [:"@",:"(?<handle>\\w*)"]
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
included do
|
18
|
+
@@pointers = {}
|
19
|
+
@@segments = {}
|
20
|
+
|
21
|
+
# Declares a pointer type on the current class object
|
22
|
+
#
|
23
|
+
# @param name [Symbol] the name of the pointer type (ie: :model, etc..)
|
24
|
+
# @param args [Hash] the hash of pointer options to be declared for this pointer
|
25
|
+
# @return [Hash] the pointer type's data hash.
|
26
|
+
def self.parses_pointer(name, **args)
|
27
|
+
if(!@@pointers[self.name.to_sym])
|
28
|
+
@@pointers[self.name.to_sym] = {}
|
29
|
+
end
|
30
|
+
if(!@@pointers[self.name.to_sym][name.to_sym])
|
31
|
+
@@pointers[self.name.to_sym][name.to_sym] = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
if(!!args[:template])
|
35
|
+
if(args[:template].is_a?(Regexp::Template))
|
36
|
+
@@pointers[self.name.to_sym][name.to_sym][:matcher] = args[:template].rx
|
37
|
+
elsif(args[:template].is_a?(Regexp))
|
38
|
+
@@pointers[self.name.to_sym][name.to_sym] = {}
|
39
|
+
@@pointers[self.name.to_sym][name.to_sym][:matcher] = args[:template]
|
40
|
+
elsif(args[:template].is_a?(String))
|
41
|
+
atoms = [:"{",:"(?<segment_name>\\w*)" ,:"}"]
|
42
|
+
segment_matcher = Regexp::Template.new(atoms:atoms)
|
43
|
+
segment_names = args[:template].scan(segment_matcher)
|
44
|
+
end
|
45
|
+
elsif(!args[:template] && SupportsPointer.const_defined?(name.to_s.upcase+"_PARSER_ATOMS"))
|
46
|
+
@@pointers[self.name.to_sym][name.to_sym][:matcher] = Regexp::Template.new(atoms:self.const_get((name.to_s.upcase+"_PARSER_ATOMS").to_sym)).rx
|
47
|
+
if(!args[:resolve])
|
48
|
+
@@pointers[self.name.to_sym][name.to_sym][:resolve] = self.const_get(name.to_s.upcase+"_RESOLVER")
|
49
|
+
end
|
50
|
+
if(!args[:generate])
|
51
|
+
@@pointers[self.name.to_sym][name.to_sym][:generate] = self.const_get(name.to_s.upcase+"_GENERATOR")
|
52
|
+
end
|
53
|
+
elsif(!args[:template] && !!args[:parse] )
|
54
|
+
@@pointers[self.name.to_sym][name.to_sym][:parser] = args[:parse]
|
55
|
+
end
|
56
|
+
if(!!args[:resolve])
|
57
|
+
@@pointers[self.name.to_sym][name.to_sym][:resolve] = args[:resolve]
|
58
|
+
end
|
59
|
+
if(!!args[:generate])
|
60
|
+
@@pointers[self.name.to_sym][name.to_sym][:generate] = args[:generate]
|
61
|
+
end
|
62
|
+
return !!@@pointers[self.name.to_sym][name.to_sym]
|
63
|
+
end
|
64
|
+
|
65
|
+
# Grants the current class access to a pointer type from another class
|
66
|
+
#
|
67
|
+
# @param name [Symbol] the name of the pointer type (ie: :model, etc..)
|
68
|
+
# @param args [Symbol] use :from to specify the class from which to grant access)
|
69
|
+
# @return [Hash] the pointer type's data hash
|
70
|
+
def self.uses_pointer(name, **args)
|
71
|
+
parser = args[:from].pointers[name.to_sym]
|
72
|
+
if(!@@pointers[self.name.to_sym])
|
73
|
+
@@pointers[self.name.to_sym] = {}
|
74
|
+
end
|
75
|
+
if(!@@pointers[self.name.to_sym][name])
|
76
|
+
@@pointers[self.name.to_sym][name] = parser
|
77
|
+
end
|
78
|
+
@@pointers[self.name.to_sym][name]
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns a hash of all pointers for a given instance's class
|
82
|
+
#
|
83
|
+
# @return [Hash] the data hash of all pointer types
|
84
|
+
def pointers
|
85
|
+
self.class.pointers
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns a hash of all pointers for a given class
|
89
|
+
#
|
90
|
+
# @return [Hash] the data hash of all pointer types
|
91
|
+
def self.pointers
|
92
|
+
if(!!@@pointers && !!@@pointers[self.name.to_sym])
|
93
|
+
if(superclass.respond_to?(:pointers))
|
94
|
+
return superclass.pointers.merge(@@pointers[self.name.to_sym])
|
95
|
+
else
|
96
|
+
return @@pointers[self.name.to_sym]
|
97
|
+
end
|
98
|
+
else
|
99
|
+
if(superclass.respond_to?(:pointers))
|
100
|
+
return superclass.pointers
|
101
|
+
else
|
102
|
+
return {}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns an array of all pointer names declared on a class
|
108
|
+
#
|
109
|
+
# @return [Array] the array of pointer type names
|
110
|
+
def self.pointer_types
|
111
|
+
return self.pointers.keys
|
112
|
+
end
|
113
|
+
|
114
|
+
# Returns whether or not a particular string matches any known pointer type
|
115
|
+
# @param ptr the string to be matched as a potential pointer
|
116
|
+
# @return [Boolean] true if ptr is a pointer, false otherwise
|
117
|
+
def self.is_pointer?(ptr)
|
118
|
+
begin
|
119
|
+
result = !!self.resolve_pointer(ptr)
|
120
|
+
rescue
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
return result
|
124
|
+
end
|
125
|
+
|
126
|
+
# Returns the pointer type name for a particular string
|
127
|
+
# @param ptr [String] the string to be matched against each pointer type
|
128
|
+
# @return [Symbol] the string's pointer type name
|
129
|
+
def self.pointer_type(ptr)
|
130
|
+
result = nil
|
131
|
+
self.pointers.each do |type, data|
|
132
|
+
if(!!data[:matcher] && ptr.match(data[:matcher]))
|
133
|
+
data = ptr.match(data[:matcher]).named_captures.symbolize_keys
|
134
|
+
if(data.none?(""))
|
135
|
+
result = type
|
136
|
+
end
|
137
|
+
elsif(!!data[:parser] && !!data[:parser].call(ptr))
|
138
|
+
result = type
|
139
|
+
end
|
140
|
+
end
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
|
144
|
+
# Parses a pointer into a Hash of data ready for resolution
|
145
|
+
# @param ptr [String] the string to be parsed as a pointer
|
146
|
+
# @param args [Hash] use :type to specify the pointer type for parsing
|
147
|
+
# @return [Hash] the data parsed from ptr
|
148
|
+
def self.parse_pointer(ptr, **args)
|
149
|
+
|
150
|
+
if(!!args[:type])
|
151
|
+
type = args[:type]
|
152
|
+
else
|
153
|
+
type = pointer_type(ptr)
|
154
|
+
end
|
155
|
+
|
156
|
+
if(!!type)
|
157
|
+
return self.send("parse_#{type.to_s}_pointer",ptr)
|
158
|
+
else
|
159
|
+
raise NameError.new("Unknown pointer type '#{pointer_name}'.", pointer_name)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Resolves a pointer into the referenced object
|
164
|
+
# @param ptr [String] the string to be parsed as a pointer
|
165
|
+
# @return the object referenced by ptr
|
166
|
+
def self.resolve_pointer(ptr)
|
167
|
+
data = self.parse_pointer(ptr)
|
168
|
+
if(!!data && !!data[:type])
|
169
|
+
return self.send("resolve_#{data[:type]}_pointer", data)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Updates the Proc object used to generate a pointer
|
174
|
+
# @param name [Symbol] the pointer type to be updated
|
175
|
+
# @param block the block to be used for pointer generation
|
176
|
+
# @return [Hash] the pointer type's data hash
|
177
|
+
def self.pointer_generation(name,&block)
|
178
|
+
@@pointers[self.name.to_sym][name.to_sym][:generate] = block.to_proc
|
179
|
+
@@pointers[self.name.to_sym][name.to_sym]
|
180
|
+
end
|
181
|
+
|
182
|
+
# Updates the Proc object used to resolve a pointer
|
183
|
+
# @param name [Symbol] the pointer type to be updated
|
184
|
+
# @param block the block to be used for pointer resolution
|
185
|
+
# @return [Hash] the pointer type's data hash
|
186
|
+
def self.pointer_resolution(name,&block)
|
187
|
+
@@pointers[self.name.to_sym][name.to_sym][:resolve] = block.to_proc
|
188
|
+
@@pointers[self.name.to_sym][name.to_sym]
|
189
|
+
end
|
190
|
+
# Implements the parse, resolve and generate behavior for each pointer type
|
191
|
+
# @param m [Symbol] the method name being called (such as ```generate_model_pointer```)
|
192
|
+
# @param args [Hash] the arguments being passed to the called method
|
193
|
+
# @return the return value for the called method
|
194
|
+
def self.method_missing(m,*args)
|
195
|
+
|
196
|
+
if(m.to_s.match?(/resolve_(?<pointer_name>\w*)_pointer/))
|
197
|
+
pointer_name = m.to_s.match(/resolve_(?<pointer_name>\w*)_pointer/)[:pointer_name]
|
198
|
+
if(self.pointers.keys.include?(pointer_name.to_sym))
|
199
|
+
if(args[0].is_a? Hash)
|
200
|
+
return self.pointers[pointer_name.to_sym][:resolve].call(args[0])
|
201
|
+
elsif(args[0].is_a? String)
|
202
|
+
return self.pointers[pointer_name.to_sym][:resolve].call(self.send("parse_#{pointer_name}_pointer", args[0]))
|
203
|
+
end
|
204
|
+
else
|
205
|
+
raise NameError.new("Unknown pointer type. Pointer type '#{pointer_name}' not defined in #{self.name}.", pointer_name)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
if(m.to_s.match?(/generate_(?<pointer_name>\w*)_pointer/))
|
210
|
+
pointer_name = m.to_s.match(/generate_(?<pointer_name>\w*)_pointer/)[:pointer_name]
|
211
|
+
if(self.pointers.keys.include?(pointer_name.to_sym) && !!self.pointers[pointer_name.to_sym][:generate])
|
212
|
+
return self.pointers[pointer_name.to_sym][:generate].call(args[0])
|
213
|
+
else
|
214
|
+
raise NameError.new("Unknown pointer type. Pointer type '#{pointer_name}' not defined in #{self.name}.", pointer_name)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
if(m.to_s.match?(/parse_(?<pointer_name>\w*)_pointer/))
|
220
|
+
pointer_name = m.to_s.match(/parse_(?<pointer_name>\w*)_pointer/)[:pointer_name]
|
221
|
+
|
222
|
+
if(self.pointers.keys.include?(pointer_name.to_sym))
|
223
|
+
if(!!self.pointers[pointer_name.to_sym][:matcher])
|
224
|
+
data = args[0].match(self.pointers[pointer_name.to_sym][:matcher]).named_captures.symbolize_keys
|
225
|
+
elsif(!!self.pointers[pointer_name.to_sym][:parser])
|
226
|
+
data = self.pointers[pointer_name.to_sym][:parser].call(args[0])
|
227
|
+
end
|
228
|
+
data[:type] = pointer_name
|
229
|
+
return data
|
230
|
+
else
|
231
|
+
raise NameError.new("Unknown pointer type. Pointer type '#{pointer_name}' not defined in #{self.name}.", pointer_name)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
# if(m.to_s.include? "generate_" && @segment_names.include?(m.to_s.split('_')[1]))
|
235
|
+
# @segments[m.to_s.split('_')[1]] = block
|
236
|
+
# end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
class Terminal
|
2
|
+
FORMATTING = {
|
3
|
+
reset:"0",
|
4
|
+
reset_bold:"21",
|
5
|
+
reset_muted:"22",
|
6
|
+
reset_italic:"22",
|
7
|
+
reset_underline:"24",
|
8
|
+
reset_blink:"25",
|
9
|
+
reset_inverted:"27",
|
10
|
+
reset_hidden:"28",
|
11
|
+
black_background: "40",
|
12
|
+
dark_gray_background: "40",
|
13
|
+
red_background: "41",
|
14
|
+
light_red_background: "41",
|
15
|
+
green_background: "42",
|
16
|
+
light_green_background: "42",
|
17
|
+
brown_orange_background: "43",
|
18
|
+
yellow_background: "43",
|
19
|
+
blue_background: "44",
|
20
|
+
light_blue_background: "44",
|
21
|
+
purple_background: "45",
|
22
|
+
light_purple_background: "45",
|
23
|
+
cyan_background: "46",
|
24
|
+
light_cyan_background: "46",
|
25
|
+
light_gray_background: "47",
|
26
|
+
white_background: "47",
|
27
|
+
bold: "1",
|
28
|
+
muted: "2",
|
29
|
+
italic: "3",
|
30
|
+
underline: "4",
|
31
|
+
blink: "5",
|
32
|
+
inverted: "7",
|
33
|
+
hidden: "8",
|
34
|
+
font1: "11",
|
35
|
+
font2: "12",
|
36
|
+
font3: "13",
|
37
|
+
font4: "14",
|
38
|
+
font5: "15",
|
39
|
+
black: "30",
|
40
|
+
dark_gray: "30",
|
41
|
+
red: "31",
|
42
|
+
light_red: "31",
|
43
|
+
green: "32",
|
44
|
+
light_green: "32",
|
45
|
+
brown_orange: "33",
|
46
|
+
yellow: "33",
|
47
|
+
blue: "34",
|
48
|
+
light_blue: "34",
|
49
|
+
purple: "35",
|
50
|
+
light_purple: "35",
|
51
|
+
cyan: "36",
|
52
|
+
light_cyan: "36",
|
53
|
+
light_gray: "37",
|
54
|
+
framed: "51",
|
55
|
+
encircled: "52",
|
56
|
+
}
|
57
|
+
def initialize(**args)
|
58
|
+
if(args[:color])
|
59
|
+
@color = args[:color]
|
60
|
+
else
|
61
|
+
@color = :reset
|
62
|
+
end
|
63
|
+
if(args[:background])
|
64
|
+
@background = args[:background]
|
65
|
+
else
|
66
|
+
@background = :reset_background
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def width(line,col)
|
71
|
+
`tput cols`
|
72
|
+
end
|
73
|
+
|
74
|
+
def height(line,col)
|
75
|
+
`tput lines`
|
76
|
+
end
|
77
|
+
|
78
|
+
def move_cursor_to(line,col)
|
79
|
+
puts "\e[#{line};#{col}H"
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_clipboard(clip_string,capture=false)
|
83
|
+
if(has_app?("xsel"))
|
84
|
+
if(!!capture)
|
85
|
+
return `#{clip_string} | xsel -i`
|
86
|
+
else
|
87
|
+
return `echo #{clip_string} | xsel -i`
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def from_clipboard
|
93
|
+
if(has_app?("xsel"))
|
94
|
+
return `xsel -o`
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def clear_clipboard
|
99
|
+
if(has_app?("xsel"))
|
100
|
+
return `xsel -c`
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def clear_terminal
|
105
|
+
puts "\e[H\e[2J\e[3J"
|
106
|
+
end
|
107
|
+
|
108
|
+
def has_app?(app_name)
|
109
|
+
return(`which #{app_name}` != "")
|
110
|
+
end
|
111
|
+
|
112
|
+
def build_formatting(name=:reset)
|
113
|
+
if(!name.is_a? Array)
|
114
|
+
name = [name]
|
115
|
+
end
|
116
|
+
return("\e[#{name.map{|n| Terminal::FORMATTING[n] }.join(';')}m")
|
117
|
+
end
|
118
|
+
|
119
|
+
def rbg(**args)
|
120
|
+
if(!!args[:red])
|
121
|
+
@red = args[:red]
|
122
|
+
elsif(!!args[:r])
|
123
|
+
@red = args[:r]
|
124
|
+
end
|
125
|
+
if(!!args[:blue])
|
126
|
+
@blue = args[:blue]
|
127
|
+
elsif(!!args[:b])
|
128
|
+
@blue = args[:b]
|
129
|
+
end
|
130
|
+
if(!!args[:green])
|
131
|
+
@green = args[:green]
|
132
|
+
elsif(!!args[:g])
|
133
|
+
@green = args[:g]
|
134
|
+
end
|
135
|
+
color_code = 16+(@red*36)+(@blue*1)+(@green*6)
|
136
|
+
if(!!args[:text])
|
137
|
+
return "\e[38;5;#{color_code}m#{args[:text]}#{build_formatting(:reset)}"
|
138
|
+
else
|
139
|
+
return color_code
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def add_formatting(text,name=:reset)
|
144
|
+
if(!name.is_a?(Array))
|
145
|
+
name = [name]
|
146
|
+
end
|
147
|
+
name.map do |n|
|
148
|
+
if(name.to_s.match(/_(\d*)_/)) # ex: "_2_" for color 2.
|
149
|
+
"\e[38;5;#{name.to_s.match(/_(\d*)_/)[1]}m#{text}#{build_formatting(:reset)}"
|
150
|
+
else
|
151
|
+
|
152
|
+
if(FORMATTING.keys
|
153
|
+
.select{|k| k.to_s.include?("reset_")}.map{|k| k.to_s.split('_')[1]}
|
154
|
+
.include?(name.to_s))
|
155
|
+
reset_formatting = build_formatting("reset_#{name.to_s}".to_sym)
|
156
|
+
else
|
157
|
+
reset_formatting = build_formatting(:reset)
|
158
|
+
|
159
|
+
end
|
160
|
+
text = "#{build_formatting(name)}#{text}#{reset_formatting}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
return text
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: supports_pointer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Greg Mikeska
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 7.0.4
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '7.0'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 7.0.4
|
33
|
+
description: A rails plugin to add human-readable pointers to models and other data
|
34
|
+
related objects.
|
35
|
+
email:
|
36
|
+
- gmikeska07@gmail.com
|
37
|
+
executables: []
|
38
|
+
extensions: []
|
39
|
+
extra_rdoc_files: []
|
40
|
+
files:
|
41
|
+
- MIT-LICENSE
|
42
|
+
- README.md
|
43
|
+
- Rakefile
|
44
|
+
- lib/core_ext/regexp.rb
|
45
|
+
- lib/core_ext/string.rb
|
46
|
+
- lib/supports_pointer.rb
|
47
|
+
- lib/supports_pointer/railtie.rb
|
48
|
+
- lib/supports_pointer/version.rb
|
49
|
+
- lib/tasks/supports_pointer_tasks.rake
|
50
|
+
- lib/terminal/terminal.rb
|
51
|
+
- lib/terminal/terminal_formatting_support.rb
|
52
|
+
homepage: https://github.com/gmikeska/supports_pointer
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
metadata:
|
56
|
+
homepage_uri: https://github.com/gmikeska/supports_pointer
|
57
|
+
source_code_uri: https://github.com/gmikeska/supports_pointer
|
58
|
+
changelog_uri: https://github.com/gmikeska/supports_pointer/blob/master/CHANGELOG.MD
|
59
|
+
post_install_message:
|
60
|
+
rdoc_options: []
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.7.0
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
requirements: []
|
74
|
+
rubygems_version: 3.2.3
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: A rails plugin to add human-readable pointers to models and other data related
|
78
|
+
objects.
|
79
|
+
test_files: []
|