supports_pointer 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|