├── Gemfile ├── Gemfile.lock ├── LICENSE ├── MIT-LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ └── stylesheets │ │ ├── application.bootstrap.scss │ │ └── hyperlayer │ │ ├── app.css │ │ ├── application.css │ │ └── prism.css ├── controllers │ ├── concerns │ │ └── .keep │ └── hyperlayer │ │ ├── application_controller.rb │ │ ├── events_controller.rb │ │ ├── example_groups_controller.rb │ │ ├── paths_controller.rb │ │ ├── runs_controller.rb │ │ └── specs_controller.rb ├── javascript │ ├── controllers │ │ ├── application.js │ │ └── index.js │ └── hyperlayer │ │ ├── application.js │ │ └── prism.js ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ └── hyperlayer │ │ ├── event.rb │ │ ├── example_group.rb │ │ ├── path.rb │ │ ├── run.rb │ │ └── spec.rb ├── services │ ├── build_file_overlay.rb │ ├── callable.rb │ ├── file_builder.rb │ ├── find_method_from_event.rb │ ├── find_method_in_path.rb │ ├── generate_buffer.rb │ ├── import_events.rb │ ├── split_methods.rb │ ├── test_one.rb │ └── update_code.rb └── views │ ├── hyperlayer │ ├── events │ │ ├── _code.html.erb │ │ ├── _event.html.erb │ │ ├── _tree-text.html.erb │ │ ├── _tree.html.erb │ │ └── index.html.erb │ ├── example_groups │ │ └── index.html.erb │ ├── paths │ │ ├── _path.html.erb │ │ ├── index.html.erb │ │ └── show.html.erb │ ├── runs │ │ └── index.html.erb │ └── specs │ │ └── index.html.erb │ └── layouts │ ├── _nav.html.erb │ └── hyperlayer │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb ├── bin └── rails ├── config ├── initializers │ └── engine_migrations.rb └── routes.rb ├── db └── migrate │ ├── 20231009165755_create_hyperlayer_events.rb │ ├── 20231009165919_create_hyperlayer_example_groups.rb │ ├── 20231009165952_create_hyperlayer_runs.rb │ ├── 20231009170101_create_hyperlayer_paths.rb │ └── 20231009170121_create_hyperlayer_specs.rb ├── hyperlayer-0.2.gem ├── hyperlayer.gemspec ├── lib ├── hyperlayer.rb ├── hyperlayer │ ├── engine.rb │ ├── method_tracer.rb │ ├── tracer.rb │ └── version.rb └── tasks │ └── listen.rake └── test ├── controllers └── .keep ├── dummy ├── Rakefile ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ └── stylesheets │ │ │ └── application.css │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── application_controller.rb │ │ └── concerns │ │ │ └── .keep │ ├── helpers │ │ └── application_helper.rb │ ├── jobs │ │ └── application_job.rb │ ├── mailers │ │ └── application_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ └── concerns │ │ │ └── .keep │ └── views │ │ └── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb ├── bin │ ├── rails │ ├── rake │ └── setup ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ └── permissions_policy.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ └── storage.yml ├── db │ └── schema.rb ├── lib │ └── assets │ │ └── .keep ├── log │ ├── .keep │ └── development.log ├── public │ ├── 404.html │ ├── 422.html │ ├── 500.html │ ├── apple-touch-icon-precomposed.png │ ├── apple-touch-icon.png │ └── favicon.ico ├── storage │ └── .keep └── tmp │ ├── .keep │ ├── local_secret.txt │ ├── pids │ └── .keep │ └── storage │ └── .keep ├── fixtures ├── files │ └── .keep └── hyperlayer │ ├── events.yml │ ├── example_groups.yml │ ├── paths.yml │ ├── runs.yml │ └── specs.yml ├── helpers └── .keep ├── hyperlayer_test.rb ├── integration ├── .keep └── navigation_test.rb ├── mailers └── .keep ├── models ├── .keep └── hyperlayer │ ├── event_test.rb │ ├── example_group_test.rb │ ├── path_test.rb │ ├── run_test.rb │ └── spec_test.rb └── test_helper.rb /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Specify your gem's dependencies in hyperlayer.gemspec. 5 | gemspec 6 | 7 | gem "puma" 8 | 9 | gem "pg" 10 | 11 | gem "sprockets-rails" 12 | 13 | # Start debugger with binding.b [https://github.com/ruby/debug] 14 | # gem "debug", ">= 1.0.0" 15 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | hyperlayer (0.1.0) 5 | bootstrap (~> 5.1.3) 6 | hashie (~> 5.0) 7 | json (~> 2.3) 8 | pry 9 | rails (>= 7.0.8) 10 | redis (~> 5.0.6) 11 | rspec (~> 3.0) 12 | 13 | GEM 14 | remote: https://rubygems.org/ 15 | specs: 16 | actioncable (7.1.0) 17 | actionpack (= 7.1.0) 18 | activesupport (= 7.1.0) 19 | nio4r (~> 2.0) 20 | websocket-driver (>= 0.6.1) 21 | zeitwerk (~> 2.6) 22 | actionmailbox (7.1.0) 23 | actionpack (= 7.1.0) 24 | activejob (= 7.1.0) 25 | activerecord (= 7.1.0) 26 | activestorage (= 7.1.0) 27 | activesupport (= 7.1.0) 28 | mail (>= 2.7.1) 29 | net-imap 30 | net-pop 31 | net-smtp 32 | actionmailer (7.1.0) 33 | actionpack (= 7.1.0) 34 | actionview (= 7.1.0) 35 | activejob (= 7.1.0) 36 | activesupport (= 7.1.0) 37 | mail (~> 2.5, >= 2.5.4) 38 | net-imap 39 | net-pop 40 | net-smtp 41 | rails-dom-testing (~> 2.2) 42 | actionpack (7.1.0) 43 | actionview (= 7.1.0) 44 | activesupport (= 7.1.0) 45 | nokogiri (>= 1.8.5) 46 | rack (>= 2.2.4) 47 | rack-session (>= 1.0.1) 48 | rack-test (>= 0.6.3) 49 | rails-dom-testing (~> 2.2) 50 | rails-html-sanitizer (~> 1.6) 51 | actiontext (7.1.0) 52 | actionpack (= 7.1.0) 53 | activerecord (= 7.1.0) 54 | activestorage (= 7.1.0) 55 | activesupport (= 7.1.0) 56 | globalid (>= 0.6.0) 57 | nokogiri (>= 1.8.5) 58 | actionview (7.1.0) 59 | activesupport (= 7.1.0) 60 | builder (~> 3.1) 61 | erubi (~> 1.11) 62 | rails-dom-testing (~> 2.2) 63 | rails-html-sanitizer (~> 1.6) 64 | activejob (7.1.0) 65 | activesupport (= 7.1.0) 66 | globalid (>= 0.3.6) 67 | activemodel (7.1.0) 68 | activesupport (= 7.1.0) 69 | activerecord (7.1.0) 70 | activemodel (= 7.1.0) 71 | activesupport (= 7.1.0) 72 | timeout (>= 0.4.0) 73 | activestorage (7.1.0) 74 | actionpack (= 7.1.0) 75 | activejob (= 7.1.0) 76 | activerecord (= 7.1.0) 77 | activesupport (= 7.1.0) 78 | marcel (~> 1.0) 79 | activesupport (7.1.0) 80 | base64 81 | bigdecimal 82 | concurrent-ruby (~> 1.0, >= 1.0.2) 83 | connection_pool (>= 2.2.5) 84 | drb 85 | i18n (>= 1.6, < 2) 86 | minitest (>= 5.1) 87 | mutex_m 88 | tzinfo (~> 2.0) 89 | autoprefixer-rails (10.4.15.0) 90 | execjs (~> 2) 91 | base64 (0.1.1) 92 | bigdecimal (3.1.3) 93 | bootstrap (5.1.3) 94 | autoprefixer-rails (>= 9.1.0) 95 | popper_js (>= 2.9.3, < 3) 96 | sassc-rails (>= 2.0.0) 97 | builder (3.2.4) 98 | coderay (1.1.3) 99 | concurrent-ruby (1.2.2) 100 | connection_pool (2.4.1) 101 | crass (1.0.6) 102 | date (3.3.3) 103 | diff-lcs (1.5.0) 104 | drb (2.1.1) 105 | ruby2_keywords 106 | erubi (1.12.0) 107 | execjs (2.9.1) 108 | ffi (1.16.3) 109 | globalid (1.2.1) 110 | activesupport (>= 6.1) 111 | hashie (5.0.0) 112 | i18n (1.14.1) 113 | concurrent-ruby (~> 1.0) 114 | io-console (0.6.0) 115 | irb (1.6.4) 116 | reline (>= 0.3.0) 117 | json (2.6.3) 118 | loofah (2.21.3) 119 | crass (~> 1.0.2) 120 | nokogiri (>= 1.12.0) 121 | mail (2.8.1) 122 | mini_mime (>= 0.1.1) 123 | net-imap 124 | net-pop 125 | net-smtp 126 | marcel (1.0.2) 127 | method_source (1.0.0) 128 | mini_mime (1.1.5) 129 | minitest (5.20.0) 130 | mutex_m (0.1.2) 131 | net-imap (0.3.7) 132 | date 133 | net-protocol 134 | net-pop (0.1.2) 135 | net-protocol 136 | net-protocol (0.2.1) 137 | timeout 138 | net-smtp (0.3.3) 139 | net-protocol 140 | nio4r (2.5.9) 141 | nokogiri (1.15.4-x86_64-darwin) 142 | racc (~> 1.4) 143 | pg (1.5.3) 144 | popper_js (2.11.8) 145 | pry (0.14.2) 146 | coderay (~> 1.1) 147 | method_source (~> 1.0) 148 | puma (6.3.0) 149 | nio4r (~> 2.0) 150 | racc (1.7.1) 151 | rack (3.0.8) 152 | rack-session (2.0.0) 153 | rack (>= 3.0.0) 154 | rack-test (2.1.0) 155 | rack (>= 1.3) 156 | rackup (2.1.0) 157 | rack (>= 3) 158 | webrick (~> 1.8) 159 | rails (7.1.0) 160 | actioncable (= 7.1.0) 161 | actionmailbox (= 7.1.0) 162 | actionmailer (= 7.1.0) 163 | actionpack (= 7.1.0) 164 | actiontext (= 7.1.0) 165 | actionview (= 7.1.0) 166 | activejob (= 7.1.0) 167 | activemodel (= 7.1.0) 168 | activerecord (= 7.1.0) 169 | activestorage (= 7.1.0) 170 | activesupport (= 7.1.0) 171 | bundler (>= 1.15.0) 172 | railties (= 7.1.0) 173 | rails-dom-testing (2.2.0) 174 | activesupport (>= 5.0.0) 175 | minitest 176 | nokogiri (>= 1.6) 177 | rails-html-sanitizer (1.6.0) 178 | loofah (~> 2.21) 179 | nokogiri (~> 1.14) 180 | railties (7.1.0) 181 | actionpack (= 7.1.0) 182 | activesupport (= 7.1.0) 183 | irb 184 | rackup (>= 1.0.0) 185 | rake (>= 12.2) 186 | thor (~> 1.0, >= 1.2.2) 187 | zeitwerk (~> 2.6) 188 | rake (13.0.6) 189 | redis (5.0.7) 190 | redis-client (>= 0.9.0) 191 | redis-client (0.17.0) 192 | connection_pool 193 | reline (0.3.4) 194 | io-console (~> 0.5) 195 | rspec (3.12.0) 196 | rspec-core (~> 3.12.0) 197 | rspec-expectations (~> 3.12.0) 198 | rspec-mocks (~> 3.12.0) 199 | rspec-core (3.12.2) 200 | rspec-support (~> 3.12.0) 201 | rspec-expectations (3.12.3) 202 | diff-lcs (>= 1.2.0, < 2.0) 203 | rspec-support (~> 3.12.0) 204 | rspec-mocks (3.12.6) 205 | diff-lcs (>= 1.2.0, < 2.0) 206 | rspec-support (~> 3.12.0) 207 | rspec-support (3.12.1) 208 | ruby2_keywords (0.0.5) 209 | sassc (2.4.0) 210 | ffi (~> 1.9) 211 | sassc-rails (2.1.2) 212 | railties (>= 4.0.0) 213 | sassc (>= 2.0) 214 | sprockets (> 3.0) 215 | sprockets-rails 216 | tilt 217 | sprockets (4.2.1) 218 | concurrent-ruby (~> 1.0) 219 | rack (>= 2.2.4, < 4) 220 | sprockets-rails (3.4.2) 221 | actionpack (>= 5.2) 222 | activesupport (>= 5.2) 223 | sprockets (>= 3.0.0) 224 | thor (1.2.2) 225 | tilt (2.3.0) 226 | timeout (0.4.0) 227 | tzinfo (2.0.6) 228 | concurrent-ruby (~> 1.0) 229 | webrick (1.8.1) 230 | websocket-driver (0.7.6) 231 | websocket-extensions (>= 0.1.0) 232 | websocket-extensions (0.1.5) 233 | zeitwerk (2.6.12) 234 | 235 | PLATFORMS 236 | x86_64-darwin-22 237 | 238 | DEPENDENCIES 239 | hyperlayer! 240 | pg 241 | puma 242 | sprockets-rails 243 | 244 | BUNDLED WITH 245 | 2.4.20 246 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 geoffw8 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Geoff Wright 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ⚡️ Hyperlayer 3 | 4 | Debug your Ruby apps 10x faster. 5 | 6 | > Depending on which research you look at, developers say they tend to spend 25–50% of their time per year on debugging. 7 | 8 | ## What is Hyperlayer? 9 | 10 | An entirely new way to visualise Ruby apps, combining application flow, state and the code itself. 11 | 12 | Removes a lot of what we’re having to do manually to debug apps. 13 | 14 | It is a paradigm shift in how we engineer software. 15 | 16 | Works out of the box, supporting any Ruby based app. 17 | 18 | This is just the beginning. 19 | 20 | ## Watch the video 21 | [![Introduction to Hyperlayer](http://img.youtube.com/vi/9iZkE8ZrFMU/0.jpg)](http://www.youtube.com/watch?v=9iZkE8ZrFMU "Introduction to Hyperlayer") 22 | 23 | * [What is Hyperlayer?](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=00m08s) (00:08) 24 | * [Why Hyperlayer?](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=04m40s) (04:40) 25 | * [Meet the demo app](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=05m31s) (05:31) 26 | * [Debugging with Hyperlayer - Viewing spec runs](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=09m34s) (09:34) 27 | * [Debugging with Hyperlayer - Application flow & state](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=10m39s) (10:39) 28 | * [Debugging with Hyperlayer - Code view](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=17m56s) (17:56) 29 | * [We’ve fixed our bug](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=25m37s) (25:37) 30 | * [Closing notes](https://www.youtube.com/watch?v=9iZkE8ZrFMU&t=27m47s) (27:47) 31 | 32 | ## Getting setup 33 | 34 | ### Installation 35 | 36 | Add the gem to your Gemfile 37 | 38 | ```ruby 39 | gem 'hyperlayer' 40 | ``` 41 | 42 | Hyperlayer adds a few tables so `bundle exec rake db:migrate` 43 | 44 | In your `spec_helper.rb` add: 45 | 46 | ```ruby 47 | RSpec.configure do |config| 48 | # Add this block 49 | config.around(:each) do |example| 50 | trace = Hyperlayer::Tracer.setup_trace 51 | trace.enable 52 | 53 | example.run 54 | 55 | trace.disable 56 | end 57 | 58 | ... 59 | end 60 | ``` 61 | 62 | Finally mount the UI by adding this to your `routes.rb`: 63 | 64 | ```ruby 65 | mount Hyperlayer::Engine => '/hyperlayer' 66 | ``` 67 | 68 | Note: You must have Redis installed and running locally. 69 | 70 | ### Using the app 71 | 72 | Once you have completed the above, running a spec will cause the events to be emitted to Redis. 73 | 74 | In order to listen to/process the events, the following must be running: 75 | ```ruby 76 | rake hyperlayer:listen 77 | ``` 78 | You should see events coming in as they are processed. 79 | 80 | Now simply load `http://localhost:3000/hyperlayer`. 81 | 82 | For better instructions I recommend you watch the video - choose one of the "Debugging with Hyperlayer" chapters above! 83 | ## Feedback 84 | > @Geoff this is the future, like how git changed collaboration, this is going to change debugging - it's amazing 85 | — [Badr Tazi](https://github.com/btazi) 86 | 87 | This is a PoC - so I'm super keen for any feedback. Please feel free to reach out! 88 | ## Author 89 | 90 | I'm [Geoff Wright](https://www.github.com/geoffw8) - Co-Founder & Chief Technology Officer at [Tembo Money](https://tembomoney.com) - the only place in the UK you can view your true house buying budget. 91 | 92 | This is a PoC - so I'm super keen for any feedback. Please feel free to reach out! 93 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 4 | load "rails/tasks/engine.rake" 5 | 6 | load "rails/tasks/statistics.rake" 7 | 8 | require "bundler/gem_tasks" 9 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/app/assets/builds/.keep -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link application.css 2 | //= link app.css 3 | //= link_tree ../stylesheets 4 | //= link_tree ../images 5 | //= link_tree ../../javascript .js 6 | //= link_tree ../../../vendor/javascript .js 7 | //= link_tree ../builds 8 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.bootstrap.scss: -------------------------------------------------------------------------------- 1 | @import 'bootstrap/scss/bootstrap'; 2 | @import 'bootstrap-icons/font/bootstrap-icons'; 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/hyperlayer/app.css: -------------------------------------------------------------------------------- 1 | .card-header { 2 | background-color: #fff; 3 | } 4 | 5 | .node { 6 | margin-bottom: 10px; 7 | } 8 | 9 | .node .card-body { 10 | padding: 0; 11 | } 12 | 13 | .return_value pre { 14 | padding: 0; 15 | margin: 20px 0; 16 | } 17 | 18 | .node .card-body pre { 19 | margin: 0; 20 | } 21 | 22 | /* Next */ 23 | 24 | .tree-node { 25 | margin-left: 15px; 26 | } 27 | 28 | a.noStyle { 29 | color: inherit; /* Inherit the color from the parent element (usually body text color) */ 30 | text-decoration: none; /* Remove the underline */ 31 | background-color: transparent; /* Ensure that there is no background color */ 32 | } 33 | 34 | /* Custom CSS to remove padding and margin from Bootstrap card */ 35 | hr.no-padding-margin { 36 | margin: 0 !important; 37 | border-radius: 0 !important; /* Optional: if you want to remove the border-radius as well */ 38 | } 39 | 40 | .no-padding-margin .card { 41 | margin: 10px 0 0 !important; 42 | border-radius: 0 !important; /* Optional: if you want to remove the border-radius as well */ 43 | } 44 | 45 | .no-padding-margin .card-body { 46 | padding: 0 !important; 47 | } 48 | -------------------------------------------------------------------------------- /app/assets/stylesheets/hyperlayer/prism.css: -------------------------------------------------------------------------------- 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} 4 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/hyperlayer/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | class ApplicationController < ActionController::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/hyperlayer/events_controller.rb: -------------------------------------------------------------------------------- 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 96 | -------------------------------------------------------------------------------- /app/controllers/hyperlayer/example_groups_controller.rb: -------------------------------------------------------------------------------- 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 10 | -------------------------------------------------------------------------------- /app/controllers/hyperlayer/paths_controller.rb: -------------------------------------------------------------------------------- 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 19 | -------------------------------------------------------------------------------- /app/controllers/hyperlayer/runs_controller.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | class RunsController < ApplicationController 3 | def index 4 | @runs = Run.all 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/hyperlayer/specs_controller.rb: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 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 } 10 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 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) 12 | -------------------------------------------------------------------------------- /app/javascript/hyperlayer/application.js: -------------------------------------------------------------------------------- 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') 8 | -------------------------------------------------------------------------------- /app/javascript/hyperlayer/prism.js: -------------------------------------------------------------------------------- 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,"&").replace(/=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&&(jg.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"+i.content+""},!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://,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\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://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(/&/,"&"))})),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var t={"included-cdata":{pattern://i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".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); 9 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/hyperlayer/event.rb: -------------------------------------------------------------------------------- 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 29 | -------------------------------------------------------------------------------- /app/models/hyperlayer/example_group.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | class ExampleGroup < ApplicationRecord 3 | belongs_to :spec 4 | has_many :events 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/hyperlayer/path.rb: -------------------------------------------------------------------------------- 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 36 | -------------------------------------------------------------------------------- /app/models/hyperlayer/run.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | class Run < ApplicationRecord 3 | has_many :specs 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/hyperlayer/spec.rb: -------------------------------------------------------------------------------- 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 9 | -------------------------------------------------------------------------------- /app/services/build_file_overlay.rb: -------------------------------------------------------------------------------- 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 = " >>> #{event.method} = #{event.return_value.inspect}" 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 27 | -------------------------------------------------------------------------------- /app/services/callable.rb: -------------------------------------------------------------------------------- 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 130 | -------------------------------------------------------------------------------- /app/services/file_builder.rb: -------------------------------------------------------------------------------- 1 | class FileBuilder 2 | def initialize(options = {}) 3 | @options = options 4 | 5 | if options[:url] 6 | parts = options[:url].split('/') 7 | 8 | if parts.count > 1 9 | options[:object] = parts.last.singularize 10 | options[:class] = parts.last.singularize.capitalize 11 | end 12 | end 13 | end 14 | 15 | attr_reader :options 16 | 17 | OPTION_KLASS_MAP = { 18 | 'authorisation' => 'Authorisation', 19 | 'url' => 'ClassDefinition', 20 | 'request' => 'BuildStrongParams', 21 | 'controller_action' => 'ControllerAction', 22 | 'skip-csrf' => 'SkipCsrf' 23 | } 24 | 25 | def call 26 | options.to_h.each_with_object(parts) do |(option, _value), output| 27 | klass = OPTION_KLASS_MAP[option] 28 | 29 | next unless klass 30 | 31 | fragments = "FileBuilder::#{klass}".constantize.call(options: options) 32 | 33 | fragments.each do |part, code| 34 | output[part] ||= [] 35 | 36 | output[part] << code 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | def parts 44 | { 45 | definition_start: [], 46 | before_actions: [], 47 | public_methods: [], 48 | private_methods: [], 49 | definition_finish: [] 50 | } 51 | end 52 | 53 | class Authorisation 54 | include Callable 55 | expects :options 56 | 57 | def call 58 | if options['authorisation'] == '1' 59 | { 60 | before_actions: authorisation 61 | } 62 | else 63 | {} 64 | end 65 | end 66 | 67 | private 68 | 69 | def authorisation 70 | template.tap do |temp| 71 | temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value| 72 | temp.gsub!("%#{value}%", options[value]) 73 | end 74 | end 75 | end 76 | 77 | def template 78 | File.read('app/views/code/authorisation.erb')[0..-2] 79 | end 80 | end 81 | 82 | class ControllerAction 83 | include Callable 84 | expects :options 85 | 86 | def call 87 | if options['controller_action'].present? 88 | { 89 | public_methods: code 90 | } 91 | else 92 | {} 93 | end 94 | end 95 | 96 | private 97 | 98 | def code 99 | template.tap do |temp| 100 | temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value| 101 | temp.gsub!("%#{value}%", options[value]) 102 | end 103 | end 104 | end 105 | 106 | def template 107 | File.read("app/views/code/controller_actions/#{controller_action}.erb")[0..-2] 108 | end 109 | 110 | def controller_action 111 | options['controller_action'] 112 | end 113 | end 114 | 115 | class TrackEvent 116 | include Callable 117 | expects :options 118 | 119 | def call 120 | if options['track_event'].present? 121 | { 122 | before_action: code, 123 | private_methods: code 124 | } 125 | else 126 | {} 127 | end 128 | end 129 | 130 | private 131 | 132 | def code 133 | template.tap do |temp| 134 | temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value| 135 | temp.gsub!("%#{value}%", options[value]) 136 | end 137 | end 138 | end 139 | 140 | def template 141 | File.read("app/views/code/controller_actions/#{controller_action}.erb")[0..-2] 142 | end 143 | 144 | def controller_action 145 | options['controller_action'] 146 | end 147 | end 148 | 149 | class ClassDefinition 150 | include Callable 151 | expects :options 152 | 153 | def call 154 | return {} unless options[:url].present? 155 | 156 | { 157 | definition_start: start, 158 | definition_finish: finish 159 | } 160 | end 161 | 162 | private 163 | 164 | def start 165 | levels.map.with_index(1) do |level, index| 166 | if index == count 167 | "class #{level.capitalize}Controller < ApplicationController" 168 | else 169 | "module #{level.capitalize}" 170 | end 171 | end 172 | end 173 | 174 | def levels 175 | options[:url].split('/').select(&:present?) 176 | end 177 | 178 | def count 179 | levels.count 180 | end 181 | 182 | def finish 183 | levels.map { 'end' } 184 | end 185 | end 186 | 187 | class SkipCsrf 188 | include Callable 189 | expects :options 190 | 191 | def call 192 | return {} unless options['skip-csrf'] == '1' 193 | 194 | { private_methods: code } 195 | end 196 | 197 | private 198 | 199 | def code 200 | template.tap do |temp| 201 | temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value| 202 | temp.gsub!("%#{value}%", options[value].to_s) 203 | end 204 | end 205 | end 206 | 207 | def template 208 | File.read('app/views/code/strong_params.erb') 209 | end 210 | end 211 | 212 | class BuildStrongParams 213 | include Callable 214 | expects :options 215 | 216 | def call 217 | return {} unless options[:request].present? 218 | 219 | options[:keys] = keys 220 | 221 | { private_methods: code } 222 | end 223 | 224 | private 225 | 226 | def code 227 | template.tap do |temp| 228 | temp.scan(/\%(.*?)\%/).flatten.uniq.each do |value| 229 | temp.gsub!("%#{value}%", options[value].to_s) 230 | end 231 | end 232 | end 233 | 234 | def template 235 | File.read('app/views/code/strong_params.erb') 236 | end 237 | 238 | def keys 239 | JSON.parse(options[:request]).keys 240 | end 241 | end 242 | 243 | ############################################ 244 | # def parts 245 | # { 246 | # definition_start: { 247 | # def_strong_params: { 248 | # open: [], 249 | # body: [], 250 | # end: [] 251 | # } 252 | # }, 253 | # before_actions: {}, 254 | # public_methods: {}, 255 | # private_methods: {}, 256 | # definition_finish: {} 257 | # } 258 | # end 259 | ############################################ 260 | end 261 | -------------------------------------------------------------------------------- /app/services/find_method_from_event.rb: -------------------------------------------------------------------------------- 1 | # require 'fast' 2 | 3 | class FindMethodFromEvent < Parser::AST::Processor 4 | include RuboCop::AST::Traversal 5 | 6 | def self.call(event) 7 | new.call(event) 8 | end 9 | 10 | # Have a "Exists?" method that checks to see if something exists 11 | # If it doesn't use an Insert class and define which section it should go into 12 | # If it does, work out the differences(?) and update them 13 | 14 | def call(event) 15 | code = File.readlines(event.path.path).join 16 | source = RuboCop::ProcessedSource.new(code, 2.7) 17 | 18 | return unless source.present? 19 | 20 | node = FindMethod.call( 21 | type: :def, 22 | name: event.method.to_sym, 23 | source: source 24 | ) 25 | 26 | return unless node.present? 27 | 28 | DisplayMethod 29 | .call(node: node, code: code) 30 | .split("\n") 31 | end 32 | 33 | private 34 | 35 | class FindMethod 36 | include Callable 37 | expects :type, :name, :source 38 | 39 | def call 40 | source.ast 41 | .each_node 42 | .map { |n| n if n.type == type && n.method_name == name } 43 | .compact 44 | end 45 | end 46 | 47 | class DisplayMethod 48 | include Callable 49 | expects :code, :node 50 | 51 | def call 52 | code[begin_pos...end_pos] 53 | end 54 | 55 | private 56 | 57 | delegate :begin_pos, :end_pos, to: :expression 58 | 59 | def expression 60 | node.first 61 | .loc 62 | .expression 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /app/services/find_method_in_path.rb: -------------------------------------------------------------------------------- 1 | # require 'fast' 2 | 3 | class FindMethodInPath < Parser::AST::Processor 4 | include RuboCop::AST::Traversal 5 | 6 | def self.call(path_id) 7 | new.call(path_id) 8 | end 9 | 10 | # Have a "Exists?" method that checks to see if something exists 11 | # If it doesn't use an Insert class and define which section it should go into 12 | # If it does, work out the differences(?) and update them 13 | 14 | def call(path_id) 15 | path = Path.find(path_id) 16 | code = File.readlines(path.path).join 17 | source = RuboCop::ProcessedSource.new(code, 2.7) 18 | 19 | path.events.call.each_with_object({}) do |event, methods| 20 | methods[event.method.to_sym] = '' 21 | 22 | node = FindMethod.call( 23 | type: :def, 24 | name: event.method.to_sym, 25 | source: source 26 | ) 27 | 28 | next unless node.present? 29 | 30 | methods[event.method.to_sym] = DisplayMethod.call(node: node, code: code) 31 | end 32 | end 33 | 34 | private 35 | 36 | class FindMethod 37 | include Callable 38 | expects :type, :name, :source 39 | 40 | def call 41 | binding.pry 42 | 43 | source.ast 44 | .each_node 45 | .map { |n| n if n.type == type && n.method_name == name } 46 | .compact 47 | end 48 | end 49 | 50 | class AddMethod 51 | include Callable 52 | expects :code, :pos 53 | 54 | def call 55 | code.insert(pos, "def code \n inserted.here! \n end") 56 | end 57 | end 58 | 59 | class Replace 60 | include Callable 61 | expects :code, :node, :with 62 | 63 | def call 64 | code[begin_pos...end_pos] = with 65 | end 66 | 67 | private 68 | 69 | delegate :begin_pos, :end_pos, to: :expression 70 | 71 | def expression 72 | node.first 73 | .arguments 74 | .first 75 | .loc 76 | .expression 77 | end 78 | end 79 | 80 | class DisplayMethod 81 | include Callable 82 | expects :code, :node 83 | 84 | def call 85 | code[begin_pos...end_pos] 86 | end 87 | 88 | private 89 | 90 | delegate :begin_pos, :end_pos, to: :expression 91 | 92 | def expression 93 | node.first 94 | .loc 95 | .expression 96 | end 97 | end 98 | end 99 | -------------------------------------------------------------------------------- /app/services/generate_buffer.rb: -------------------------------------------------------------------------------- 1 | class GenerateBuffer < Parser::AST::Processor 2 | include RuboCop::AST::Traversal 3 | include Callable 4 | 5 | expects :params 6 | 7 | DEFAULT_BUFFER = <<~NODE 8 | module Fresco 9 | class Dev 10 | end 11 | end 12 | NODE 13 | 14 | def call 15 | @code = DEFAULT_BUFFER 16 | @buffer = RuboCop::ProcessedSource.new(@code, 2.7) 17 | 18 | fields.each do |field| 19 | @buffer = "#{self.class}::#{field.classify}" 20 | .constantize 21 | .call(buffer: @buffer, params: params) 22 | end 23 | 24 | @buffer 25 | end 26 | 27 | private 28 | 29 | def fields 30 | params.slice(:url).keys 31 | end 32 | 33 | class Url 34 | include Callable 35 | expects :buffer, :params 36 | 37 | def call 38 | node = FindMethod.call( 39 | meth_name: :module, 40 | source: buffer 41 | ) 42 | 43 | Replace.call( 44 | buffer: code, 45 | node: node, 46 | with: InitializeModuleClass.call(url: params[:url]) 47 | ) 48 | end 49 | end 50 | 51 | class InitializeModuleClass 52 | include Callable 53 | expects :url 54 | 55 | def call 56 | return {} unless url.present? 57 | 58 | [start, finish].flatten.join("\n") 59 | end 60 | 61 | private 62 | 63 | def start 64 | levels.map.with_index(1) do |level, index| 65 | if index == count 66 | "class #{level.capitalize}Controller < ApplicationController" 67 | else 68 | "module #{level.capitalize}" 69 | end 70 | end 71 | end 72 | 73 | def levels 74 | url.split('/').select(&:present?) 75 | end 76 | 77 | def count 78 | levels.count 79 | end 80 | 81 | def finish 82 | levels.map { 'end' } 83 | end 84 | end 85 | 86 | class FindMethod 87 | include Callable 88 | expects :meth_name, :source 89 | 90 | def call 91 | source.ast 92 | .each_node 93 | .map { |n| n if n.type == :send && n.method_name == meth_name } 94 | .compact 95 | end 96 | end 97 | 98 | class Replace 99 | include Callable 100 | expects :code, :node, :with 101 | 102 | def call 103 | code[start...finish] = with 104 | end 105 | 106 | private 107 | 108 | def start 109 | node.first.arguments.first.loc.expression.begin_pos 110 | end 111 | 112 | def finish 113 | node.first.arguments.first.loc.expression.end_pos 114 | end 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /app/services/import_events.rb: -------------------------------------------------------------------------------- 1 | # ["#{tp.path}:#{tp.lineno}", tp.defined_class, tp.event, tp.method_id, rv] 2 | 3 | require 'csv' 4 | 5 | class ImportEvents 6 | def self.call 7 | # Connect to a Redis server 8 | redis = Redis.new(url: 'redis://localhost:6379') 9 | 10 | begin 11 | # Subscribe to a Redis channel 12 | redis.subscribe('events') do |on| 13 | on.message do |channel, message| 14 | puts "Received message on channel #{channel}: #{message}" 15 | 16 | event = Hashie::Mash.new(JSON.parse(message)) 17 | 18 | run = Hyperlayer::Run.where(process: event.spec.process).first_or_create 19 | path = Hyperlayer::Path.where(path: event.path).first_or_create 20 | 21 | spec_file = event.spec.tree.reverse.first 22 | spec = run.specs.where( 23 | location: spec_file.file_path, 24 | description: spec_file.description, # sometimes a class, could also be a string for a feature spec, 25 | data: spec_file.except(:file_path, :description) 26 | ).first_or_create 27 | 28 | example_groups = event.spec.tree.reverse[1..] 29 | groups = example_groups.map do |example_group| 30 | spec.example_groups.where( 31 | location: example_group.location, 32 | description: example_group.description, 33 | data: example_group.except(:location, :description) 34 | ).first_or_create 35 | end 36 | 37 | example_group = groups.last 38 | example_group.events.create( 39 | path: path, 40 | line_number: event.line_number, 41 | defined_class: event.defined_class, 42 | event_type: event.event, 43 | method: event.method_id, 44 | return_value: event.return_value, 45 | arguments: event.arguments, 46 | variables: event.variables 47 | ) 48 | end 49 | end 50 | rescue Redis::BaseConnectionError => error 51 | puts "#{error}, retrying in 1s" 52 | sleep 1 53 | retry 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /app/services/split_methods.rb: -------------------------------------------------------------------------------- 1 | # require 'fast' 2 | 3 | class SplitMethods < Parser::AST::Processor 4 | include RuboCop::AST::Traversal 5 | 6 | def self.call(event) 7 | new.call(event) 8 | end 9 | 10 | def call(event) 11 | raw_code = File.readlines(event.path.path) 12 | code = raw_code.join 13 | source = RuboCop::ProcessedSource.new(code, 3.2) 14 | 15 | method_name = event.method.to_sym 16 | 17 | method = source.ast 18 | .each_node 19 | .select do |n| 20 | # Will need to add method name here, and the delegate param 21 | # 22 | n.type == :def && method_name == n.method_name # || n.type == :send && n.method_name == :delegate 23 | end 24 | 25 | method = method.first 26 | 27 | DisplayMethod.call(code: code, node: method) 28 | rescue => e 29 | Rails.logger.info("Missing method: #{method_name}") 30 | end 31 | 32 | private 33 | 34 | class DisplayMethod 35 | include Callable 36 | expects :code, :node 37 | 38 | def call 39 | code[begin_pos...end_pos] 40 | end 41 | 42 | private 43 | 44 | delegate :begin_pos, :end_pos, to: :expression 45 | 46 | def expression 47 | node.loc.expression 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /app/services/test_one.rb: -------------------------------------------------------------------------------- 1 | class TestOne 2 | include Callable 3 | expects :path 4 | 5 | def call 6 | @file = [] 7 | @raw_file = File.readlines(path.path).join 8 | 9 | return @raw_file 10 | 11 | path.events.where(event_type: 'return').each do |event| 12 | href = "#{event.method}" 13 | 14 | matches = @raw_file.gsub!(/#{event.method}/, href) 15 | end 16 | 17 | return @raw_file 18 | 19 | 20 | 21 | @display_file = @raw_file.map.with_index(1) do |line, index| 22 | @file << line 23 | 24 | events = path.events.where(event_type: 'return', line_number: index) 25 | 26 | events.map do |event| 27 | @file << ">>> #{event.return_value.inspect} (#{event.method})" 28 | @file << "\n" 29 | end 30 | end 31 | 32 | @file 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/services/update_code.rb: -------------------------------------------------------------------------------- 1 | # require 'fast' 2 | 3 | class UpdateCode < Parser::AST::Processor 4 | include RuboCop::AST::Traversal 5 | 6 | def self.call(id, code, method) 7 | new.call(id, code, method) 8 | end 9 | 10 | LOCATION_MAP = { 11 | Send: :expression, 12 | MethodDefinition: :name 13 | } 14 | 15 | def call(id, code, method) 16 | path = Path.find(id) 17 | code = code.presence || File.read(path.path) 18 | 19 | method = method.to_sym 20 | 21 | # First find all instances 22 | 23 | source = RuboCop::ProcessedSource.new(code, 2.7) 24 | nodes = FindMethod.call( 25 | source: source, 26 | name: method, 27 | method_types: [:def, :send] 28 | ) 29 | 30 | nodes.each do |node| 31 | # Then look again and change them all one by one 32 | source = RuboCop::ProcessedSource.new(code, 2.7) 33 | node = FindMethod.call( 34 | source: source, 35 | name: method, 36 | method_types: [:def, :send] 37 | ).first 38 | 39 | type = node.loc.class.to_s.split("::").last.to_sym 40 | 41 | ReplaceCode.call( 42 | code: code, 43 | node: node, 44 | location: LOCATION_MAP[type], 45 | with: 'geoff', 46 | ) 47 | end 48 | 49 | code 50 | 51 | # Add "after/before" which allows to position 52 | # AddMethod.call(code: code, pos: 150) 53 | end 54 | 55 | private 56 | 57 | class FindMethod 58 | include Callable 59 | expects :source, :name, :method_types 60 | 61 | def call 62 | source.ast 63 | .each_node 64 | .map { |n| n if method_types.include?(n.type) && n.method_name == name } 65 | .compact 66 | end 67 | end 68 | 69 | class AddMethod 70 | include Callable 71 | expects :code, :pos 72 | 73 | def call 74 | code.insert(pos, "def code \n inserted.here! \n end") 75 | end 76 | end 77 | 78 | class ShowCode 79 | include Callable 80 | expects :code, :node, :location, :with 81 | 82 | def call 83 | code[begin_pos...end_pos] 84 | end 85 | 86 | private 87 | 88 | delegate :begin_pos, :end_pos, to: :expression 89 | 90 | def expression 91 | node.loc.send(location) 92 | end 93 | end 94 | 95 | class ReplaceCode 96 | include Callable 97 | expects :code, :node, :location, :with 98 | 99 | def call 100 | code[begin_pos...end_pos] = with 101 | end 102 | 103 | private 104 | 105 | delegate :begin_pos, :end_pos, to: :expression 106 | 107 | def expression 108 | node.loc.send(location) 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /app/views/hyperlayer/events/_code.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= event.defined_class %> 5 |
6 |
7 | #<%= event.method %> 8 |
9 |
10 |
11 | <%= event.arguments %> 12 |
13 |
14 | 15 |
16 |
17 |       
18 |   <%= SplitMethods.call(event) %>
19 |       
20 |     
21 | 22 | <% if false %> 23 |
24 |       
25 |           <%= event.variables %>
26 |       
27 |       
28 | <% end %> 29 | 30 |
31 |     
32 |   <%= event.return_value_events&.return_value.presence || 'nil' %>
33 |     
34 |     
35 |
36 |
37 | -------------------------------------------------------------------------------- /app/views/hyperlayer/events/_event.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
 4 |       
 5 |   <%= SplitMethods.call(event) %>
 6 |       
 7 |     
8 | 9 |
10 |     
11 |   <%= event.return_value_events&.return_value.presence || 'nil' %>
12 |     
13 |     
14 |
15 |
16 | -------------------------------------------------------------------------------- /app/views/hyperlayer/events/_tree-text.html.erb: -------------------------------------------------------------------------------- 1 | <% branches.each do |branch| %> 2 | 22 | <% end %> 23 | -------------------------------------------------------------------------------- /app/views/hyperlayer/events/_tree.html.erb: -------------------------------------------------------------------------------- 1 | <% branches.each do |branch| %> 2 |
3 | <% branch.each do |event, sub_branches| %> 4 |
"> 5 |
6 | <%= render "code", event: event %> 7 |
8 |
9 | 10 | <% if sub_branches.present? %> 11 | <%= render partial: 'tree', locals: { branches: sub_branches } %> 12 | <% end %> 13 | <% end %> 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/hyperlayer/events/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= render 'tree-text', branches: @tree %> 5 |
6 |
7 |
8 | 9 | 10 | 26 | 27 | 42 | -------------------------------------------------------------------------------- /app/views/hyperlayer/example_groups/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
<%= @spec.location %>
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @example_groups.each do |example_group| %> 14 | 15 | 19 | 20 | <% end %> 21 | 22 |
Example Groups
16 | <%= link_to(example_group.description, run_spec_example_group_events_path(@run, params[:spec_id], example_group)) %>
17 | <%= example_group.location %> 18 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /app/views/hyperlayer/paths/_path.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% path.events.distinct.order(line_number: :asc).each do |event| %> 14 | 15 | 16 | 17 | 22 | 23 | 24 | <% end %> 25 | 26 |
MethodTypeValueLine #
<%= event.method %><%= event.event_type %> 18 | 19 | <%= event.return_value %> 20 | 21 | <%= event.line_number %>
27 |
28 |
29 |
30 |
31 | <%= path.path.split('/')[5..-1].join('/') %> (<%= path.id %>) 32 |
33 | 34 |
35 |
<%= path.overlay.html_safe %>
36 |
37 |
38 |
39 |
40 | <% if false %> 41 |
42 |
43 | <%= path.path.split('/')[5..-1].join('/') %> (<%= path.id %>) 44 |
45 | 46 |
47 |
<%= path.overlay_code.html_safe %>
48 |
49 |
50 | <% end %> 51 |
52 |
53 | -------------------------------------------------------------------------------- /app/views/hyperlayer/paths/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% @paths.each do |path| %> 3 | <%= render 'path', path: path %> 4 | <% end %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/hyperlayer/paths/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render 'path', path: @path %> 3 |
4 | -------------------------------------------------------------------------------- /app/views/hyperlayer/runs/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% @runs.each do |run| %> 12 | 13 | 17 | 18 | <% end %> 19 | 20 |
Spec
14 | <%= link_to(run.process, run_specs_path(run)) %>
15 | <%= run.specs.count %> specs 16 |
21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /app/views/hyperlayer/specs/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | <% @specs.each do |spec| %> 12 | 13 | 18 | 19 | <% end %> 20 | 21 |
Spec
14 | <%= link_to(spec.description, run_spec_example_groups_path(@run, spec)) %>
15 | <%= spec.location %>
16 | <%= spec.example_groups.count %> example groups 17 |
22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /app/views/layouts/_nav.html.erb: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /app/views/layouts/hyperlayer/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hyperlayer 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag 'hyperlayer/application', media: 'all', 'data-turbolinks-track': 'reload' %> 10 | <%= stylesheet_link_tag 'hyperlayer/app', media: 'all', 'data-turbolinks-track': 'reload' %> 11 | 12 | 13 | 14 | 15 | 16 | 17 | <%= render 'layouts/nav' %> 18 | 19 |
20 |
21 | <%= yield %> 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/views/layouts/hyperlayer/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/hyperlayer/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails gems 3 | # installed from the root of your application. 4 | 5 | ENGINE_ROOT = File.expand_path("..", __dir__) 6 | ENGINE_PATH = File.expand_path("../lib/hyperlayer/engine", __dir__) 7 | APP_PATH = File.expand_path("../test/dummy/config/application", __dir__) 8 | 9 | # Set up gems listed in the Gemfile. 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 11 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 12 | 13 | require "rails/all" 14 | require "rails/engine/commands" 15 | -------------------------------------------------------------------------------- /config/initializers/engine_migrations.rb: -------------------------------------------------------------------------------- 1 | Hyperlayer::Engine.config.paths['db/migrate'].expanded.each do |expanded_path| 2 | Rails.application.config.paths['db/migrate'] << expanded_path 3 | end 4 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Hyperlayer::Engine.routes.draw do 2 | resources :runs, only: [:index] do 3 | resources :specs, only: [:index, :show] do 4 | resources :example_groups do 5 | resources :events, only: [:index] 6 | end 7 | end 8 | end 9 | 10 | root 'runs#index' 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20231009165755_create_hyperlayer_events.rb: -------------------------------------------------------------------------------- 1 | class CreateHyperlayerEvents < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :hyperlayer_events do |t| 4 | t.string :location 5 | t.string :defined_class 6 | t.string :event_type 7 | t.string :method 8 | t.text :return_value 9 | t.integer :path_id 10 | t.integer :line_number 11 | t.jsonb :spec 12 | t.integer :example_group_id 13 | t.jsonb :arguments 14 | t.jsonb :variables 15 | 16 | t.timestamps 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20231009165919_create_hyperlayer_example_groups.rb: -------------------------------------------------------------------------------- 1 | class CreateHyperlayerExampleGroups < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :hyperlayer_example_groups do |t| 4 | t.string :location 5 | t.string :description 6 | t.integer :spec_id 7 | t.jsonb :data 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20231009165952_create_hyperlayer_runs.rb: -------------------------------------------------------------------------------- 1 | class CreateHyperlayerRuns < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :hyperlayer_runs do |t| 4 | t.integer :process 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20231009170101_create_hyperlayer_paths.rb: -------------------------------------------------------------------------------- 1 | class CreateHyperlayerPaths < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :hyperlayer_paths do |t| 4 | t.string :path 5 | t.boolean :spec 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20231009170121_create_hyperlayer_specs.rb: -------------------------------------------------------------------------------- 1 | class CreateHyperlayerSpecs < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :hyperlayer_specs do |t| 4 | t.string :location 5 | t.string :description 6 | t.jsonb :data 7 | t.integer :run_id 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /hyperlayer-0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/hyperlayer-0.2.gem -------------------------------------------------------------------------------- /hyperlayer.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/hyperlayer/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "hyperlayer" 5 | spec.version = Hyperlayer::VERSION 6 | spec.authors = ["Geoff Wright"] 7 | spec.email = ["g746025@gmail.com"] 8 | spec.homepage = "https://github.com/geoffw8/hyperlayer" 9 | spec.summary = "Debug Ruby apps 10x faster." 10 | spec.description = "Debug Ruby apps 10x faster." 11 | spec.license = "MIT" 12 | 13 | # Prevent pushing this gem to RubyGems.org. To allow pushes either set the "allowed_push_host" 14 | # to allow pushing to a single host or delete this section to allow pushing to any host. 15 | spec.metadata["allowed_push_host"] = "https://rubygems.org" 16 | 17 | spec.metadata["homepage_uri"] = spec.homepage 18 | spec.metadata["source_code_uri"] = "https://github.com/geoffw8/hyperlayer" 19 | spec.metadata["changelog_uri"] = "https://github.com/geoffw8/hyperlayer" 20 | 21 | spec.files = Dir.chdir(File.expand_path(__dir__)) do 22 | Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 23 | end 24 | 25 | # Add dependencies 26 | spec.add_dependency "rails", ">= 7.0.8" 27 | spec.add_dependency 'json', '~> 2.3' 28 | spec.add_dependency 'redis', '~> 5.0.6' 29 | spec.add_dependency 'hashie', '~> 5.0' 30 | spec.add_dependency 'rspec', '~> 3.0' 31 | spec.add_dependency 'bootstrap', '~> 5.1.3' 32 | spec.add_dependency 'pry' 33 | end 34 | -------------------------------------------------------------------------------- /lib/hyperlayer.rb: -------------------------------------------------------------------------------- 1 | require "hyperlayer/version" 2 | require "hyperlayer/engine" 3 | 4 | require 'json' 5 | require 'redis' 6 | require 'hashie' 7 | require 'pry' 8 | require 'hyperlayer/method_tracer' 9 | require 'hyperlayer/tracer' 10 | 11 | module Hyperlayer 12 | def self.trace_rspec! 13 | Tracer.trace_rspec! 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/hyperlayer/engine.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | class Engine < ::Rails::Engine 3 | isolate_namespace Hyperlayer 4 | 5 | initializer "hyperlayer.use_parent_app_database" do 6 | database_config = Rails.configuration.database_configuration[Rails.env] 7 | ActiveRecord::Base.establish_connection(database_config) 8 | end 9 | 10 | initializer "hyperlayer.load_migrations" do |app| 11 | unless app.root.to_s.match?(config.root.to_s) 12 | config.paths["db/migrate"].expanded.each do |expanded_path| 13 | app.config.paths["db/migrate"] << expanded_path 14 | end 15 | end 16 | end 17 | 18 | initializer "hyperlayer.assets.precompile" do |app| 19 | app.config.assets.precompile += %w( hyperlayer/application.css hyperlayer/app.css hyperlayer/prism.css hyperlayer/prism.js hyperlayer/application.js ) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/hyperlayer/method_tracer.rb: -------------------------------------------------------------------------------- 1 | # lib/hyperlayer/method_tracer.rb 2 | 3 | class MethodTracer 4 | def self.arguments_for(tp) 5 | method_params = tp.self.method(tp.method_id).parameters 6 | method_params.map do |type, name| 7 | [name, tp.binding.local_variable_get(name)] 8 | end.to_h 9 | rescue => e 10 | {} # Return an empty hash in case of errors 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/hyperlayer/tracer.rb: -------------------------------------------------------------------------------- 1 | # lib/hyperlayer/tracer.rb 2 | 3 | module Hyperlayer 4 | module Tracer 5 | def self.trace_rspec! 6 | trace = setup_trace 7 | trace.enable 8 | 9 | RSpec.configure do |config| 10 | config.after(:suite) { trace.disable } 11 | end 12 | end 13 | 14 | private 15 | 16 | def self.setup_trace 17 | TracePoint.new(:call, :return) do |tp| 18 | next unless relevant_path?(tp.path) 19 | 20 | event_data = extract_event_data(tp) 21 | 22 | Redis.new(url: 'redis://localhost:6379').publish('events', event_data.to_json) 23 | end 24 | end 25 | 26 | def self.relevant_path?(path) 27 | path.include?('tembo') 28 | end 29 | 30 | def self.extract_event_data(tp) 31 | args = MethodTracer.arguments_for(tp) 32 | 33 | event = { 34 | path: tp.path, 35 | line_number: tp.lineno, 36 | defined_class: tp.defined_class.to_s, 37 | event: tp.event, 38 | method_id: tp.method_id, 39 | arguments: args, 40 | return_value: (tp.event == :return ? tp.return_value.to_s : nil) 41 | } 42 | 43 | # RSpec metadata extraction (as per your original code) 44 | if defined?(RSpec) && RSpec.respond_to?(:current_example) && RSpec.current_example 45 | metadata = Hashie::Mash.new(RSpec.current_example.metadata) 46 | 47 | tree = [] 48 | tree << metadata.except(:example_group, :block) 49 | tree << metadata.example_group.except(:parent_example_group, :block) 50 | 51 | parent = metadata.example_group.parent_example_group 52 | 53 | while parent 54 | tree << parent.except(:parent_example_group, :block) 55 | parent = parent.parent_example_group 56 | end 57 | 58 | tree.each do |branch| 59 | branch['described_class'] = branch['described_class'].to_s 60 | branch['description_args'] = [branch['description_args'].first.to_s] 61 | 62 | if branch['execution_result'] 63 | branch['execution_result'] = branch['execution_result'].started_at.to_f 64 | end 65 | end 66 | 67 | event[:spec] = { 68 | process: Process.pid, 69 | run: metadata.execution_result.started_at.to_f.to_s, 70 | tree: tree 71 | } 72 | end 73 | 74 | event[:return_value] = nil if event[:return_value]&.class != String 75 | event 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/hyperlayer/version.rb: -------------------------------------------------------------------------------- 1 | module Hyperlayer 2 | VERSION = "0.2" 3 | end 4 | -------------------------------------------------------------------------------- /lib/tasks/listen.rake: -------------------------------------------------------------------------------- 1 | namespace :hyperlayer do 2 | desc "Run the hyperlayer listen method" 3 | 4 | task listen: :environment do 5 | # Call the method you want to run here 6 | ImportEvents.call 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/controllers/.keep -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/dummy/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link hyperlayer_manifest.js 4 | -------------------------------------------------------------------------------- /test/dummy/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/app/assets/images/.keep -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/app/models/concerns/.keep -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dummy 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | <%= stylesheet_link_tag "application" %> 10 | 11 | 12 | 13 | <%= yield %> 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/dummy/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | require "hyperlayer" 9 | 10 | module Dummy 11 | class Application < Rails::Application 12 | config.load_defaults Rails::VERSION::STRING.to_f 13 | 14 | # For compatibility with applications that use this config 15 | config.action_controller.include_all_helpers = false 16 | 17 | # Configuration for the application, engines, and railties goes here. 18 | # 19 | # These settings can be overridden in specific environments using the files 20 | # in config/environments, which are processed later. 21 | # 22 | # config.time_zone = "Central Time (US & Canada)" 23 | # config.eager_load_paths << Rails.root.join("extras") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) 3 | 4 | require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) 5 | $LOAD_PATH.unshift File.expand_path("../../../lib", __dir__) 6 | -------------------------------------------------------------------------------- /test/dummy/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: dummy_production 11 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # # PostgreSQL. Versions 9.3 and up are supported. 2 | # # 3 | # # Install the pg driver: 4 | # # gem install pg 5 | # # On macOS with Homebrew: 6 | # # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # # On macOS with MacPorts: 8 | # # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # # On Windows: 10 | # # gem install pg 11 | # # Choose the win32 build. 12 | # # Install PostgreSQL and put its /bin directory on your path. 13 | # # 14 | # # Configure Using Gemfile 15 | # # gem "pg" 16 | # # 17 | # default: &default 18 | # adapter: postgresql 19 | # encoding: unicode 20 | # # For details on connection pooling, see Rails configuration guide 21 | # # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | # pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | # development: 25 | # <<: *default 26 | # database: hypergen_development 27 | 28 | # # The specified database role being used to connect to postgres. 29 | # # To create additional roles in postgres see `$ createuser --help`. 30 | # # When left blank, postgres will use the default role. This is 31 | # # the same name as the operating system user running Rails. 32 | # #username: hypergen 33 | 34 | # # The password associated with the postgres role (username). 35 | # #password: 36 | 37 | # # Connect on a TCP socket. Omitted by default since the client uses a 38 | # # domain socket that doesn't need configuration. Windows does not have 39 | # # domain sockets, so uncomment these lines. 40 | # #host: localhost 41 | 42 | # # The TCP port the server listens on. Defaults to 5432. 43 | # # If your server runs on a different port number, change accordingly. 44 | # #port: 5432 45 | 46 | # # Schema search path. The server defaults to $user,public 47 | # #schema_search_path: myapp,sharedapp,public 48 | 49 | # # Minimum log levels, in increasing order: 50 | # # debug5, debug4, debug3, debug2, debug1, 51 | # # log, notice, warning, error, fatal, and panic 52 | # # Defaults to warning. 53 | # #min_messages: notice 54 | 55 | # # Warning: The database defined as "test" will be erased and 56 | # # re-generated from your development database when you run "rake". 57 | # # Do not set this db to the same as development or production. 58 | # test: 59 | # <<: *default 60 | # database: hypergen_test 61 | 62 | # # As with config/credentials.yml, you never want to store sensitive information, 63 | # # like your database password, in your source code. If your source code is 64 | # # ever seen by anyone, they now have access to your database. 65 | # # 66 | # # Instead, provide the password or a full connection URL as an environment 67 | # # variable when you boot the app. For example: 68 | # # 69 | # # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 70 | # # 71 | # # If the connection URL is provided in the special DATABASE_URL environment 72 | # # variable, Rails will automatically merge its configuration values on top of 73 | # # the values provided in this file. Alternatively, you can specify a connection 74 | # # URL environment variable explicitly: 75 | # # 76 | # # production: 77 | # # url: <%= ENV["MY_APP_DATABASE_URL"] %> 78 | # # 79 | # # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 80 | # # for a full overview on how database connection configuration can be specified. 81 | # # 82 | # production: 83 | # <<: *default 84 | # database: hypergen_production 85 | # username: hypergen 86 | # password: <%= ENV["HYPERGEN_DATABASE_PASSWORD"] %> 87 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded any time 7 | # it changes. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Store uploaded files on the local file system (see config/storage.yml for options). 37 | config.active_storage.service = :local 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raise an error on page load if there are pending migrations. 54 | config.active_record.migration_error = :page_load 55 | 56 | # Highlight code that triggered database queries in logs. 57 | config.active_record.verbose_query_logs = true 58 | 59 | # Suppress logger output for asset requests. 60 | config.assets.quiet = true 61 | 62 | # Raises error for missing translations. 63 | # config.i18n.raise_on_missing_translations = true 64 | 65 | # Annotate rendered view with file names. 66 | # config.action_view.annotate_rendered_view_with_filenames = true 67 | 68 | # Uncomment if you wish to allow Action Cable access from any origin. 69 | # config.action_cable.disable_request_forgery_protection = true 70 | end 71 | -------------------------------------------------------------------------------- /test/dummy/config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.asset_host = "http://assets.example.com" 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 39 | 40 | # Store uploaded files on the local file system (see config/storage.yml for options). 41 | config.active_storage.service = :local 42 | 43 | # Mount Action Cable outside main process or domain. 44 | # config.action_cable.mount_path = nil 45 | # config.action_cable.url = "wss://example.com/cable" 46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Include generic and useful information about system operation, but avoid logging too much 52 | # information to avoid inadvertent exposure of personally identifiable information (PII). 53 | config.log_level = :info 54 | 55 | # Prepend all log lines with the following tags. 56 | config.log_tags = [ :request_id ] 57 | 58 | # Use a different cache store in production. 59 | # config.cache_store = :mem_cache_store 60 | 61 | # Use a real queuing backend for Active Job (and separate queues per environment). 62 | # config.active_job.queue_adapter = :resque 63 | # config.active_job.queue_name_prefix = "dummy_production" 64 | 65 | config.action_mailer.perform_caching = false 66 | 67 | # Ignore bad email addresses and do not raise email delivery errors. 68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 69 | # config.action_mailer.raise_delivery_errors = false 70 | 71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 72 | # the I18n.default_locale when a translation cannot be found). 73 | config.i18n.fallbacks = true 74 | 75 | # Don't log any deprecations. 76 | config.active_support.report_deprecations = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | 81 | # Use a different logger for distributed setups. 82 | # require "syslog/logger" 83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 84 | 85 | if ENV["RAILS_LOG_TO_STDOUT"].present? 86 | logger = ActiveSupport::Logger.new(STDOUT) 87 | logger.formatter = config.log_formatter 88 | config.logger = ActiveSupport::TaggedLogging.new(logger) 89 | end 90 | 91 | # Do not dump schema after migrations. 92 | config.active_record.dump_schema_after_migration = false 93 | end 94 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV["CI"].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | end 61 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /test/dummy/config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `bin/rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | mount Hyperlayer::Engine => "/hyperlayer" 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.1].define(version: 2023_10_09_185249) do 14 | # These are extensions that must be enabled in order to support this database 15 | enable_extension "plpgsql" 16 | 17 | create_table "events", force: :cascade do |t| 18 | t.string "location" 19 | t.string "defined_class" 20 | t.string "event_type" 21 | t.string "method" 22 | t.text "return_value" 23 | t.datetime "created_at", null: false 24 | t.datetime "updated_at", null: false 25 | t.integer "path_id" 26 | t.integer "line_number" 27 | t.jsonb "spec" 28 | t.bigint "example_group_id", null: false 29 | t.jsonb "arguments" 30 | t.jsonb "variables" 31 | t.index ["example_group_id"], name: "index_events_on_example_group_id" 32 | end 33 | 34 | create_table "example_groups", force: :cascade do |t| 35 | t.string "location" 36 | t.string "description" 37 | t.bigint "spec_id", null: false 38 | t.jsonb "data" 39 | t.datetime "created_at", null: false 40 | t.datetime "updated_at", null: false 41 | t.index ["spec_id"], name: "index_example_groups_on_spec_id" 42 | end 43 | 44 | create_table "hyperlayer_events", force: :cascade do |t| 45 | t.string "location" 46 | t.string "defined_class" 47 | t.string "event_type" 48 | t.string "method" 49 | t.text "return_value" 50 | t.integer "path_id" 51 | t.integer "line_number" 52 | t.jsonb "spec" 53 | t.integer "example_group_id" 54 | t.jsonb "arguments" 55 | t.jsonb "variables" 56 | t.datetime "created_at", null: false 57 | t.datetime "updated_at", null: false 58 | end 59 | 60 | create_table "hyperlayer_example_groups", force: :cascade do |t| 61 | t.string "location" 62 | t.string "description" 63 | t.integer "spec_id" 64 | t.jsonb "data" 65 | t.datetime "created_at", null: false 66 | t.datetime "updated_at", null: false 67 | end 68 | 69 | create_table "hyperlayer_paths", force: :cascade do |t| 70 | t.string "path" 71 | t.boolean "spec" 72 | t.datetime "created_at", null: false 73 | t.datetime "updated_at", null: false 74 | end 75 | 76 | create_table "hyperlayer_runs", force: :cascade do |t| 77 | t.integer "process" 78 | t.datetime "created_at", null: false 79 | t.datetime "updated_at", null: false 80 | end 81 | 82 | create_table "hyperlayer_specs", force: :cascade do |t| 83 | t.string "location" 84 | t.string "description" 85 | t.jsonb "data" 86 | t.datetime "created_at", null: false 87 | t.datetime "updated_at", null: false 88 | t.integer "run_id" 89 | end 90 | 91 | create_table "paths", force: :cascade do |t| 92 | t.string "path" 93 | t.datetime "created_at", null: false 94 | t.datetime "updated_at", null: false 95 | t.boolean "spec" 96 | end 97 | 98 | create_table "runs", force: :cascade do |t| 99 | t.integer "process" 100 | t.datetime "created_at", null: false 101 | t.datetime "updated_at", null: false 102 | end 103 | 104 | create_table "specs", force: :cascade do |t| 105 | t.string "location" 106 | t.string "description" 107 | t.jsonb "data" 108 | t.datetime "created_at", null: false 109 | t.datetime "updated_at", null: false 110 | t.bigint "run_id", null: false 111 | t.index ["run_id"], name: "index_specs_on_run_id" 112 | end 113 | 114 | add_foreign_key "events", "example_groups" 115 | add_foreign_key "example_groups", "specs" 116 | add_foreign_key "specs", "runs" 117 | end 118 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/lib/assets/.keep -------------------------------------------------------------------------------- /test/dummy/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/log/.keep -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /test/dummy/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/public/apple-touch-icon.png -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/public/favicon.ico -------------------------------------------------------------------------------- /test/dummy/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/storage/.keep -------------------------------------------------------------------------------- /test/dummy/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/tmp/.keep -------------------------------------------------------------------------------- /test/dummy/tmp/local_secret.txt: -------------------------------------------------------------------------------- 1 | edf674606a0c7eeee40e579f92ff937c1365b4d90f10fd950478a075e707e6b5e95ac585e059b4f63dc3730197170115ca24a9b3f26fa3e290d2b9c2f4a03905 -------------------------------------------------------------------------------- /test/dummy/tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/tmp/pids/.keep -------------------------------------------------------------------------------- /test/dummy/tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/dummy/tmp/storage/.keep -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/hyperlayer/events.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | location: MyString 5 | defined_class: MyString 6 | event_type: MyString 7 | method: MyString 8 | return_value: MyText 9 | path_id: 1 10 | line_number: 1 11 | spec: 12 | example_group_id: 1 13 | arguments: 14 | variables: 15 | 16 | two: 17 | location: MyString 18 | defined_class: MyString 19 | event_type: MyString 20 | method: MyString 21 | return_value: MyText 22 | path_id: 1 23 | line_number: 1 24 | spec: 25 | example_group_id: 1 26 | arguments: 27 | variables: 28 | -------------------------------------------------------------------------------- /test/fixtures/hyperlayer/example_groups.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | location: MyString 5 | description: MyString 6 | spec_id: 1 7 | data: 8 | 9 | two: 10 | location: MyString 11 | description: MyString 12 | spec_id: 1 13 | data: 14 | -------------------------------------------------------------------------------- /test/fixtures/hyperlayer/paths.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | path: MyString 5 | spec: false 6 | 7 | two: 8 | path: MyString 9 | spec: false 10 | -------------------------------------------------------------------------------- /test/fixtures/hyperlayer/runs.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | process: 1 5 | 6 | two: 7 | process: 1 8 | -------------------------------------------------------------------------------- /test/fixtures/hyperlayer/specs.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | location: MyString 5 | description: MyString 6 | data: 7 | 8 | two: 9 | location: MyString 10 | description: MyString 11 | data: 12 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/helpers/.keep -------------------------------------------------------------------------------- /test/hyperlayer_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class HyperlayerTest < ActiveSupport::TestCase 4 | test "it has a version number" do 5 | assert Hyperlayer::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/integration/.keep -------------------------------------------------------------------------------- /test/integration/navigation_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class NavigationTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/geoffw8/hyperlayer/de578295a3dc71a3300daa3b2d00ea7e09672a9d/test/models/.keep -------------------------------------------------------------------------------- /test/models/hyperlayer/event_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Hyperlayer 4 | class EventTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/models/hyperlayer/example_group_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Hyperlayer 4 | class ExampleGroupTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/models/hyperlayer/path_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Hyperlayer 4 | class PathTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/models/hyperlayer/run_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Hyperlayer 4 | class RunTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/models/hyperlayer/spec_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Hyperlayer 4 | class SpecTest < ActiveSupport::TestCase 5 | # test "the truth" do 6 | # assert true 7 | # end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | 4 | require_relative "../test/dummy/config/environment" 5 | ActiveRecord::Migrator.migrations_paths = [File.expand_path("../test/dummy/db/migrate", __dir__)] 6 | ActiveRecord::Migrator.migrations_paths << File.expand_path("../db/migrate", __dir__) 7 | require "rails/test_help" 8 | 9 | # Load fixtures from the engine 10 | if ActiveSupport::TestCase.respond_to?(:fixture_path=) 11 | ActiveSupport::TestCase.fixture_path = File.expand_path("fixtures", __dir__) 12 | ActionDispatch::IntegrationTest.fixture_path = ActiveSupport::TestCase.fixture_path 13 | ActiveSupport::TestCase.file_fixture_path = ActiveSupport::TestCase.fixture_path + "/files" 14 | ActiveSupport::TestCase.fixtures :all 15 | end 16 | --------------------------------------------------------------------------------