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 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: []