supports_pointer 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -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,4 @@
1
+ module SupportsPointer
2
+ class Railtie < ::Rails::Railtie
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module SupportsPointer
2
+ VERSION = "0.5.0"
3
+ 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,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :supports_pointer do
3
+ # # Task goes here
4
+ # 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
@@ -0,0 +1,6 @@
1
+ require_relative "terminal"
2
+ module TerminalFormattingSupport
3
+ def self.included(base)
4
+ @terminal = Terminal.new()
5
+ end
6
+ 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: []