hyperlayer 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +38 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/manifest.js +7 -0
  6. data/app/assets/stylesheets/application.bootstrap.scss +2 -0
  7. data/app/assets/stylesheets/hyperlayer/app.css +47 -0
  8. data/app/assets/stylesheets/hyperlayer/application.css +18774 -0
  9. data/app/assets/stylesheets/hyperlayer/prism.css +3 -0
  10. data/app/controllers/hyperlayer/application_controller.rb +4 -0
  11. data/app/controllers/hyperlayer/events_controller.rb +95 -0
  12. data/app/controllers/hyperlayer/example_groups_controller.rb +9 -0
  13. data/app/controllers/hyperlayer/paths_controller.rb +18 -0
  14. data/app/controllers/hyperlayer/runs_controller.rb +7 -0
  15. data/app/controllers/hyperlayer/specs_controller.rb +8 -0
  16. data/app/javascript/controllers/application.js +9 -0
  17. data/app/javascript/controllers/index.js +11 -0
  18. data/app/javascript/hyperlayer/application.js +7 -0
  19. data/app/javascript/hyperlayer/prism.js +8 -0
  20. data/app/models/application_record.rb +3 -0
  21. data/app/models/hyperlayer/event.rb +28 -0
  22. data/app/models/hyperlayer/example_group.rb +6 -0
  23. data/app/models/hyperlayer/path.rb +35 -0
  24. data/app/models/hyperlayer/run.rb +5 -0
  25. data/app/models/hyperlayer/spec.rb +8 -0
  26. data/app/services/build_file_overlay.rb +26 -0
  27. data/app/services/callable.rb +129 -0
  28. data/app/services/file_builder.rb +260 -0
  29. data/app/services/find_method_from_event.rb +65 -0
  30. data/app/services/find_method_in_path.rb +98 -0
  31. data/app/services/generate_buffer.rb +116 -0
  32. data/app/services/import_events.rb +56 -0
  33. data/app/services/split_methods.rb +50 -0
  34. data/app/services/test_one.rb +34 -0
  35. data/app/services/update_code.rb +111 -0
  36. data/app/views/hyperlayer/events/_code.html.erb +36 -0
  37. data/app/views/hyperlayer/events/_event.html.erb +15 -0
  38. data/app/views/hyperlayer/events/_tree-text.html.erb +22 -0
  39. data/app/views/hyperlayer/events/_tree.html.erb +15 -0
  40. data/app/views/hyperlayer/events/index.html.erb +41 -0
  41. data/app/views/hyperlayer/example_groups/index.html.erb +25 -0
  42. data/app/views/hyperlayer/paths/_path.html.erb +52 -0
  43. data/app/views/hyperlayer/paths/index.html.erb +5 -0
  44. data/app/views/hyperlayer/paths/show.html.erb +3 -0
  45. data/app/views/hyperlayer/runs/index.html.erb +23 -0
  46. data/app/views/hyperlayer/specs/index.html.erb +24 -0
  47. data/app/views/layouts/_nav.html.erb +8 -0
  48. data/app/views/layouts/hyperlayer/application.html.erb +30 -0
  49. data/app/views/layouts/hyperlayer/mailer.html.erb +13 -0
  50. data/app/views/layouts/hyperlayer/mailer.text.erb +1 -0
  51. data/config/initializers/engine_migrations.rb +3 -0
  52. data/config/routes.rb +11 -0
  53. data/db/migrate/20231009165755_create_hyperlayer_events.rb +19 -0
  54. data/db/migrate/20231009165919_create_hyperlayer_example_groups.rb +12 -0
  55. data/db/migrate/20231009165952_create_hyperlayer_runs.rb +9 -0
  56. data/db/migrate/20231009170101_create_hyperlayer_paths.rb +10 -0
  57. data/db/migrate/20231009170121_create_hyperlayer_specs.rb +12 -0
  58. data/lib/hyperlayer/engine.rb +22 -0
  59. data/lib/hyperlayer/method_tracer.rb +12 -0
  60. data/lib/hyperlayer/tracer.rb +78 -0
  61. data/lib/hyperlayer/version.rb +3 -0
  62. data/lib/hyperlayer.rb +15 -0
  63. data/lib/tasks/listen.rake +8 -0
  64. metadata +207 -0
@@ -0,0 +1,3 @@
1
+ /* PrismJS 1.29.0
2
+ https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+ruby */
3
+ code[class*=language-],pre[class*=language-]{color:#000;background:0 0;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#9a6e3a;background:hsla(0,0%,100%,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.class-name,.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}
@@ -0,0 +1,4 @@
1
+ module Hyperlayer
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,95 @@
1
+ module Hyperlayer
2
+ class EventsController < ApplicationController
3
+ APP_DIR = Rails.root.to_s.split('/').last
4
+
5
+ def index
6
+ @spec = Spec.find(params[:spec_id])
7
+
8
+ @example_group = @spec.example_groups.find(params[:example_group_id])
9
+
10
+ @events = @example_group.events
11
+ .call
12
+ .includes(:path)
13
+ .where(path: { spec: nil })
14
+ .order(id: :asc)
15
+ .select { |e| e.path.path.include?(APP_DIR) }
16
+
17
+ @groups = []
18
+ used_ids = []
19
+
20
+ @events.each.with_index do |event, index|
21
+ next if used_ids.flatten.include?(event.id)
22
+
23
+ batch = { event.defined_class => [] }
24
+
25
+ count = index
26
+
27
+ while true do
28
+ next_event = @events[count]
29
+
30
+ break if next_event.nil? || next_event.defined_class != event.defined_class
31
+
32
+ batch[event.defined_class] << @events[count]
33
+
34
+ count += 1
35
+ end
36
+
37
+ @groups << batch
38
+ used_ids << batch.values.flatten.map(&:id)
39
+ used_ids.flatten
40
+ end
41
+
42
+ # first_klass = @groups.first.keys.first
43
+ # first_event = @groups.first.values.first.first
44
+
45
+ # @tree = [
46
+ # {
47
+ # klass: first_klass,
48
+ # method: first_event.method,
49
+ # return_value_id: first_event.return_value_events&.id,
50
+ # children: []
51
+ # }
52
+ # ]
53
+
54
+ # @groups.each do |group|
55
+ # klass = group.keys.first
56
+ # events = group.values.first
57
+
58
+ # events.each do |event|
59
+ # while event.id < @tree.last[:return_value_id]
60
+ # {
61
+ # klass: klass,
62
+ # method: event.method,
63
+ # return_value_id: event.return_value_events&.id,
64
+ # children: []
65
+ # }
66
+ # end
67
+ # end
68
+ # end
69
+
70
+ @tree = BuildTree.call(@events)
71
+ end
72
+ end
73
+
74
+ class BuildTree
75
+ def self.call(events, used_ids = [])
76
+ branches = []
77
+ current_branch = nil
78
+
79
+ events.each.with_index do |event, index|
80
+ if !used_ids.include?(event.id)
81
+ rest_of_events = events[(index + 1)..-1].select { |e| e.id < event.return_value_events&.id }
82
+ used_ids << event.id
83
+
84
+ current_branch = {
85
+ event => BuildTree.call(rest_of_events, used_ids)
86
+ }
87
+
88
+ branches.push(current_branch)
89
+ end
90
+ end
91
+
92
+ branches
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,9 @@
1
+ module Hyperlayer
2
+ class ExampleGroupsController < ApplicationController
3
+ def index
4
+ @run = Hyperlayer::Run.find(params[:run_id])
5
+ @spec = @run.specs.find(params[:spec_id])
6
+ @example_groups = @spec.example_groups
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,18 @@
1
+ module Hyperlayer
2
+ class PathsController < ApplicationController
3
+ def index
4
+ # @paths = Path.includes(:events).all.limit(500).order(id: :asc)
5
+
6
+ @paths = [
7
+ { id: '1', position: { x: 0, y: 0 }, data: { label: '1' } },
8
+ { id: '2', position: { x: 0, y: 75 }, data: { label: '2' } },
9
+ ]
10
+
11
+ render json: @paths.to_json
12
+ end
13
+
14
+ def show
15
+ @path = Path.find(params[:id])
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,7 @@
1
+ module Hyperlayer
2
+ class RunsController < ApplicationController
3
+ def index
4
+ @runs = Run.all
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ module Hyperlayer
2
+ class SpecsController < ApplicationController
3
+ def index
4
+ @run = Run.find(params[:run_id])
5
+ @specs = @run.specs
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ import { Application } from "@hotwired/stimulus"
2
+
3
+ const application = Application.start()
4
+
5
+ // Configure Stimulus development experience
6
+ application.debug = false
7
+ window.Stimulus = application
8
+
9
+ export { application }
@@ -0,0 +1,11 @@
1
+ // Import and register all your controllers from the importmap under controllers/*
2
+
3
+ import { application } from "controllers/application"
4
+
5
+ // Eager load all controllers defined in the import map under controllers/**/*_controller
6
+ import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading"
7
+ eagerLoadControllersFrom("controllers", application)
8
+
9
+ // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!)
10
+ // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading"
11
+ // lazyLoadControllersFrom("controllers", application)
@@ -0,0 +1,7 @@
1
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
2
+ import "@hotwired/turbo-rails"
3
+ import "prism"
4
+ import "controllers"
5
+ import "bootstrap"
6
+
7
+ console.log('hi')
@@ -0,0 +1,8 @@
1
+ /* PrismJS 1.29.0
2
+ https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript+ruby */
3
+ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(e){var n=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,r={},a={manual:e.Prism&&e.Prism.manual,disableWorkerMessageHandler:e.Prism&&e.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof i?new i(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(n,t){var r,i;switch(t=t||{},a.util.type(n)){case"Object":if(i=a.util.objId(n),t[i])return t[i];for(var l in r={},t[i]=r,n)n.hasOwnProperty(l)&&(r[l]=e(n[l],t));return r;case"Array":return i=a.util.objId(n),t[i]?t[i]:(r=[],t[i]=r,n.forEach((function(n,a){r[a]=e(n,t)})),r);default:return n}},getLanguage:function(e){for(;e;){var t=n.exec(e.className);if(t)return t[1].toLowerCase();e=e.parentElement}return"none"},setLanguage:function(e,t){e.className=e.className.replace(RegExp(n,"gi"),""),e.classList.add("language-"+t)},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(r){var e=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(r.stack)||[])[1];if(e){var n=document.getElementsByTagName("script");for(var t in n)if(n[t].src==e)return n[t]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:r,plaintext:r,text:r,txt:r,extend:function(e,n){var t=a.util.clone(a.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(e,n,t,r){var i=(r=r||a.languages)[e],l={};for(var o in i)if(i.hasOwnProperty(o)){if(o==n)for(var s in t)t.hasOwnProperty(s)&&(l[s]=t[s]);t.hasOwnProperty(o)||(l[o]=i[o])}var u=r[e];return r[e]=l,a.languages.DFS(a.languages,(function(n,t){t===u&&n!=e&&(this[n]=l)})),l},DFS:function e(n,t,r,i){i=i||{};var l=a.util.objId;for(var o in n)if(n.hasOwnProperty(o)){t.call(n,o,n[o],r||o);var s=n[o],u=a.util.type(s);"Object"!==u||i[l(s)]?"Array"!==u||i[l(s)]||(i[l(s)]=!0,e(s,t,o,i)):(i[l(s)]=!0,e(s,t,null,i))}}},plugins:{},highlightAll:function(e,n){a.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};a.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),a.hooks.run("before-all-elements-highlight",r);for(var i,l=0;i=r.elements[l++];)a.highlightElement(i,!0===n,r.callback)},highlightElement:function(n,t,r){var i=a.util.getLanguage(n),l=a.languages[i];a.util.setLanguage(n,i);var o=n.parentElement;o&&"pre"===o.nodeName.toLowerCase()&&a.util.setLanguage(o,i);var s={element:n,language:i,grammar:l,code:n.textContent};function u(e){s.highlightedCode=e,a.hooks.run("before-insert",s),s.element.innerHTML=s.highlightedCode,a.hooks.run("after-highlight",s),a.hooks.run("complete",s),r&&r.call(s.element)}if(a.hooks.run("before-sanity-check",s),(o=s.element.parentElement)&&"pre"===o.nodeName.toLowerCase()&&!o.hasAttribute("tabindex")&&o.setAttribute("tabindex","0"),!s.code)return a.hooks.run("complete",s),void(r&&r.call(s.element));if(a.hooks.run("before-highlight",s),s.grammar)if(t&&e.Worker){var c=new Worker(a.filename);c.onmessage=function(e){u(e.data)},c.postMessage(JSON.stringify({language:s.language,code:s.code,immediateClose:!0}))}else u(a.highlight(s.code,s.grammar,s.language));else u(a.util.encode(s.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};if(a.hooks.run("before-tokenize",r),!r.grammar)throw new Error('The language "'+r.language+'" has no grammar.');return r.tokens=a.tokenize(r.code,r.grammar),a.hooks.run("after-tokenize",r),i.stringify(a.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new s;return u(a,a.head,e),o(e,a,n,a.head,0),function(e){for(var n=[],t=e.head.next;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=a.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=a.hooks.all[e];if(t&&t.length)for(var r,i=0;r=t[i++];)r(n)}},Token:i};function i(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function l(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function o(e,n,t,r,s,g){for(var f in t)if(t.hasOwnProperty(f)&&t[f]){var h=t[f];h=Array.isArray(h)?h:[h];for(var d=0;d<h.length;++d){if(g&&g.cause==f+","+d)return;var v=h[d],p=v.inside,m=!!v.lookbehind,y=!!v.greedy,k=v.alias;if(y&&!v.pattern.global){var x=v.pattern.toString().match(/[imsuy]*$/)[0];v.pattern=RegExp(v.pattern.source,x+"g")}for(var b=v.pattern||v,w=r.next,A=s;w!==n.tail&&!(g&&A>=g.reach);A+=w.value.length,w=w.next){var E=w.value;if(n.length>e.length)return;if(!(E instanceof i)){var P,L=1;if(y){if(!(P=l(b,A,e,m))||P.index>=e.length)break;var S=P.index,O=P.index+P[0].length,j=A;for(j+=w.value.length;S>=j;)j+=(w=w.next).value.length;if(A=j-=w.value.length,w.value instanceof i)continue;for(var C=w;C!==n.tail&&(j<O||"string"==typeof C.value);C=C.next)L++,j+=C.value.length;L--,E=e.slice(A,j),P.index-=A}else if(!(P=l(b,0,E,m)))continue;S=P.index;var N=P[0],_=E.slice(0,S),M=E.slice(S+N.length),W=A+E.length;g&&W>g.reach&&(g.reach=W);var z=w.prev;if(_&&(z=u(n,z,_),A+=_.length),c(n,z,L),w=u(n,z,new i(f,p?a.tokenize(N,p):N,k,N)),M&&u(n,w,M),L>1){var I={cause:f+","+d,reach:W};o(e,n,t,w.prev,A,I),g&&I.reach>g.reach&&(g.reach=I.reach)}}}}}}function s(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function u(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function c(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;n.next=r,r.prev=n,e.length-=a}if(e.Prism=a,i.stringify=function e(n,t){if("string"==typeof n)return n;if(Array.isArray(n)){var r="";return n.forEach((function(n){r+=e(n,t)})),r}var i={type:n.type,content:e(n.content,t),tag:"span",classes:["token",n.type],attributes:{},language:t},l=n.alias;l&&(Array.isArray(l)?Array.prototype.push.apply(i.classes,l):i.classes.push(l)),a.hooks.run("wrap",i);var o="";for(var s in i.attributes)o+=" "+s+'="'+(i.attributes[s]||"").replace(/"/g,"&quot;")+'"';return"<"+i.tag+' class="'+i.classes.join(" ")+'"'+o+">"+i.content+"</"+i.tag+">"},!e.document)return e.addEventListener?(a.disableWorkerMessageHandler||e.addEventListener("message",(function(n){var t=JSON.parse(n.data),r=t.language,i=t.code,l=t.immediateClose;e.postMessage(a.highlight(i,a.languages[r],r)),l&&e.close()}),!1),a):a;var g=a.util.currentScript();function f(){a.manual||a.highlightAll()}if(g&&(a.filename=g.src,g.hasAttribute("data-manual")&&(a.manual=!0)),!a.manual){var h=document.readyState;"loading"===h||"interactive"===h&&g&&g.defer?document.addEventListener("DOMContentLoaded",f):window.requestAnimationFrame?window.requestAnimationFrame(f):window.setTimeout(f,16)}return a}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
4
+ Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",(function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&amp;/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,(function(){return a})),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
5
+ !function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|"+e.source+")*?(?:;|(?=\\s*\\{))"),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
6
+ Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
7
+ Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),Prism.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
8
+ !function(e){e.languages.ruby=e.languages.extend("clike",{comment:{pattern:/#.*|^=begin\s[\s\S]*?^=end/m,greedy:!0},"class-name":{pattern:/(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/,operator:/\.{2,3}|&\.|===|<?=>|[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/,punctuation:/[(){}[\].,;]/}),e.languages.insertBefore("ruby","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}});var n={pattern:/((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/,lookbehind:!0,inside:{content:{pattern:/^(#\{)[\s\S]+(?=\}$)/,lookbehind:!0,inside:e.languages.ruby},delimiter:{pattern:/^#\{|\}$/,alias:"punctuation"}}};delete e.languages.ruby.function;var t="(?:"+["([^a-zA-Z0-9\\s{(\\[<=])(?:(?!\\1)[^\\\\]|\\\\[^])*\\1","\\((?:[^()\\\\]|\\\\[^]|\\((?:[^()\\\\]|\\\\[^])*\\))*\\)","\\{(?:[^{}\\\\]|\\\\[^]|\\{(?:[^{}\\\\]|\\\\[^])*\\})*\\}","\\[(?:[^\\[\\]\\\\]|\\\\[^]|\\[(?:[^\\[\\]\\\\]|\\\\[^])*\\])*\\]","<(?:[^<>\\\\]|\\\\[^]|<(?:[^<>\\\\]|\\\\[^])*>)*>"].join("|")+")",i='(?:"(?:\\\\.|[^"\\\\\r\n])*"|(?:\\b[a-zA-Z_]\\w*|[^\\s\0-\\x7F]+)[?!]?|\\$.)';e.languages.insertBefore("ruby","keyword",{"regex-literal":[{pattern:RegExp("%r"+t+"[egimnosux]{0,6}"),greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}},{pattern:/(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/,lookbehind:!0,greedy:!0,inside:{interpolation:n,regex:/[\s\S]+/}}],variable:/[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/,symbol:[{pattern:RegExp("(^|[^:]):"+i),lookbehind:!0,greedy:!0},{pattern:RegExp("([\r\n{(,][ \t]*)"+i+"(?=:(?!:))"),lookbehind:!0,greedy:!0}],"method-definition":{pattern:/(\bdef\s+)\w+(?:\s*\.\s*\w+)?/,lookbehind:!0,inside:{function:/\b\w+$/,keyword:/^self\b/,"class-name":/^\w+/,punctuation:/\./}}}),e.languages.insertBefore("ruby","string",{"string-literal":[{pattern:RegExp("%[qQiIwWs]?"+t),greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/,greedy:!0,inside:{interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?/}},interpolation:n,string:/[\s\S]+/}},{pattern:/<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i,alias:"heredoc-string",greedy:!0,inside:{delimiter:{pattern:/^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i,inside:{symbol:/\b\w+/,punctuation:/^<<[-~]?'|'$/}},string:/[\s\S]+/}}],"command-literal":[{pattern:RegExp("%x"+t),greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}},{pattern:/`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/,greedy:!0,inside:{interpolation:n,command:{pattern:/[\s\S]+/,alias:"string"}}}]}),delete e.languages.ruby.string,e.languages.insertBefore("ruby","number",{builtin:/\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/,constant:/\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/}),e.languages.rb=e.languages.ruby}(Prism);
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ primary_abstract_class
3
+ end
@@ -0,0 +1,28 @@
1
+ module Hyperlayer
2
+ class Event < ApplicationRecord
3
+ belongs_to :path
4
+ belongs_to :example_group
5
+
6
+ scope :call, -> { where(event_type: 'call') }
7
+ scope :return, -> { where(event_type: 'return') }
8
+
9
+ def return_value_events
10
+ Event.where(
11
+ 'id > ?', id
12
+ ).find_by(
13
+ event_type: 'return',
14
+ defined_class: defined_class,
15
+ method: method,
16
+ example_group: example_group
17
+ )
18
+ end
19
+
20
+ def spec
21
+ Hashie::Mash.new(self[:spec])
22
+ end
23
+
24
+ def method_code
25
+ FindMethodFromEvent.call(self)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,6 @@
1
+ module Hyperlayer
2
+ class ExampleGroup < ApplicationRecord
3
+ belongs_to :spec
4
+ has_many :events
5
+ end
6
+ end
@@ -0,0 +1,35 @@
1
+ module Hyperlayer
2
+ class Path < ApplicationRecord
3
+ has_many :events
4
+
5
+ before_create :set_spec
6
+
7
+ def set_spec
8
+ return unless path.include?('spec')
9
+
10
+ self.spec = true
11
+ end
12
+
13
+ def overlay
14
+ BuildFileOverlay.call(path: self)
15
+ end
16
+
17
+ def test_one
18
+ TestOne.call(path: self)
19
+ end
20
+
21
+ def edited
22
+ Ast.call(id)
23
+ end
24
+
25
+ def overlay_code
26
+ @code = ''
27
+
28
+ events.where(event_type: 'return').each do |event|
29
+ @code = UpdateCode.call(id, @code, event.method)
30
+ end
31
+
32
+ @code
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ module Hyperlayer
2
+ class Run < ApplicationRecord
3
+ has_many :specs
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ module Hyperlayer
2
+ class Spec < ApplicationRecord
3
+ belongs_to :run
4
+
5
+ has_many :example_groups
6
+ has_many :events
7
+ end
8
+ end
@@ -0,0 +1,26 @@
1
+ class BuildFileOverlay
2
+ include Callable
3
+ expects :path
4
+
5
+ def call
6
+ @file = []
7
+ @raw_file = File.readlines(path.path)
8
+
9
+ @display_file = @raw_file.map.with_index(1) do |line, index|
10
+ @file << "#{index}: #{line}"
11
+
12
+ events = path.events.where(event_type: 'return', line_number: index)
13
+
14
+ events.each do |event|
15
+ item = " <i style='color: #ccc'>>>> #{event.method} = #{event.return_value.inspect}</i>"
16
+
17
+ next if @file.include?(item)
18
+
19
+ @file << item
20
+ @file << "\n"
21
+ end
22
+ end
23
+
24
+ @file.join
25
+ end
26
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ # BOILERPLATE SERVICE SETUP
4
+ #
5
+ # I know we are using interactors a lot at the moment but not
6
+ # _all_ things are an interactor and there are times where we
7
+ # still need to use a good ol' fashion ruby class.
8
+ #
9
+ # What this _does_ give us however is at least the setup portion
10
+ # of an Interactor, simply include this class in a ruby class
11
+ # and you'll get:
12
+ #
13
+ # class SomeClass
14
+ # def self.call(some:, args:, here:)
15
+ # new(some, args, here).call
16
+ # end
17
+
18
+ # def inititalize(some, args, here)
19
+ # @some = some
20
+ # @args = args
21
+ # @here = here
22
+ # end
23
+
24
+ # def call
25
+ # do some stuff in here
26
+ # end
27
+
28
+ # private
29
+
30
+ # attr_reader :some, :args, :here
31
+ # end
32
+
33
+ # You can now:
34
+
35
+ # class SomeClass
36
+ # include Callable
37
+
38
+ # expects :some, :args, :here
39
+
40
+ # def call
41
+ # do some stuff in here
42
+ # end
43
+ # end
44
+ #
45
+ module Callable # rubocop:disable
46
+ extend ActiveSupport::Concern
47
+
48
+ class_methods do
49
+ concerning :ExpectedValues do
50
+ def expects(*keys)
51
+ @expected_keys = keys
52
+ end
53
+
54
+ def received_expected_keys(kwargs)
55
+ kwargs.keys.map(&:to_sym) - Array(@optional_keys)
56
+ end
57
+
58
+ def validate_expectations_met!(kwargs)
59
+ missing_keys = Array(@expected_keys) - received_expected_keys(kwargs)
60
+
61
+ return unless missing_keys.any?
62
+
63
+ raise ArgumentError, "Expectations not met - missing keys: #{missing_keys}"
64
+ end
65
+ end
66
+
67
+ concerning :OptionalValues do
68
+ def optional(*keys)
69
+ @optional_keys = keys
70
+ end
71
+
72
+ def missing_optional_keys(kwargs)
73
+ @optional_keys&.select do |k, _v|
74
+ !kwargs.keys.include?(k)
75
+ end || []
76
+ end
77
+
78
+ def missing_optionals(kwargs)
79
+ missing_optional_keys(kwargs).index_with { |_k| nil }
80
+ end
81
+ end
82
+
83
+ def readable_attributes(kwargs)
84
+ kwargs.merge(missing_optionals(kwargs))
85
+ end
86
+
87
+ def call(kwargs)
88
+ kwargs.deep_symbolize_keys!
89
+
90
+ validate_expectations_met!(kwargs)
91
+
92
+ new(readable_attributes(kwargs)).call
93
+ end
94
+ end
95
+
96
+ # OVERRIDING VALUES IN INITIALIZE
97
+ #
98
+ # If you want to override values in initialize then you can put this in your
99
+ # calling class and it will let you transform one of the values:
100
+ #
101
+ # def initialize(args)
102
+ # super
103
+ #
104
+ # @quantity = args[:quantity].to_i
105
+ # end
106
+ #
107
+ # Its not super clean, but there might be times it needs to be done.
108
+ #
109
+ def initialize(kwargs)
110
+ # Set each of them as an instance var, i.e.
111
+ # @something = something
112
+ readable_attributes = self.class.readable_attributes(kwargs)
113
+
114
+ readable_attributes.each do |key, value|
115
+ instance_variable_set("@#{key}", value)
116
+ end
117
+
118
+ @context = Hashie::Mash.new(kwargs)
119
+
120
+ # Then we set them as attr_readers so we can call them
121
+ # i.e. something
122
+ class_eval do
123
+ private
124
+
125
+ attr_reader(*readable_attributes.keys.map(&:to_sym))
126
+ attr_reader(:context)
127
+ end
128
+ end
129
+ end