rails-playground 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +67 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/javascripts/playground/application.js +17 -0
  6. data/app/assets/javascripts/playground/controllers/codemirror_controller.js +49 -0
  7. data/app/assets/javascripts/playground/controllers/hello_controller.js +7 -0
  8. data/app/assets/javascripts/playground/controllers/playground_controller.js +12 -0
  9. data/app/assets/javascripts/playground/controllers/search_scripts_controller.js +20 -0
  10. data/app/assets/javascripts/playground/controllers/select_script_controller.js +8 -0
  11. data/app/assets/stylesheets/playground/application.scss +28 -0
  12. data/app/controllers/playground/application_controller.rb +4 -0
  13. data/app/controllers/playground/consoles_controller.rb +7 -0
  14. data/app/controllers/playground/scripts_controller.rb +49 -0
  15. data/app/helpers/playground/application_helper.rb +4 -0
  16. data/app/helpers/playground/consoles_helper.rb +4 -0
  17. data/app/helpers/playground/scripts_helper.rb +34 -0
  18. data/app/jobs/playground/application_job.rb +4 -0
  19. data/app/mailers/playground/application_mailer.rb +6 -0
  20. data/app/models/playground/application_record.rb +5 -0
  21. data/app/views/layouts/playground/application.html.erb +14 -0
  22. data/app/views/playground/consoles/show.html.erb +20 -0
  23. data/app/views/playground/scripts/_script.html.erb +5 -0
  24. data/app/views/playground/scripts/_script_select_box.html.erb +3 -0
  25. data/app/views/playground/scripts/index.html.erb +1 -0
  26. data/app/views/playground/scripts/index.turbo_stream.erb +1 -0
  27. data/app/views/playground/scripts/show.html.erb +1 -0
  28. data/app/views/playground/scripts/show.turbo_stream.erb +1 -0
  29. data/config/routes.rb +6 -0
  30. data/lib/playground/engine.rb +14 -0
  31. data/lib/playground/templates/index.html.erb +22 -0
  32. data/lib/playground/version.rb +3 -0
  33. data/lib/playground.rb +20 -0
  34. data/lib/tasks/playground_tasks.rake +4 -0
  35. data/vendor/javascripts/playground/application.js +41 -0
  36. data/vendor/stylesheets/playground/application.css +57 -0
  37. data/vendor/stylesheets/playground/application.css.map +1 -0
  38. metadata +109 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df198165430aba4c303eda2c7ea181c913f58bfa8314b51ed3d456341746689a
4
+ data.tar.gz: e25a29c31228b830b9a40890e89e796741407287a9178f8cde7c266d32a1e817
5
+ SHA512:
6
+ metadata.gz: 04b3f859d616b122bce51a4c624693a6b3cd784f1a398b69b524845f83ec6d01171e5905f6589e2da021b4a006d7f40f955ba21ab03c7314d8c6a1c8ccaac3b6
7
+ data.tar.gz: 6408f5d0f1b941ae0912fc5404cce2d6722c795990ffeb6f3300722412862fa94f75dbeea2e935d5d3c22476883bb986448c0776adeb8a2461c8e4972dbdeed0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2022 Alexandre Barret
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # Playground
2
+
3
+ Back-and-forths from your editor and your rails console is a thing of the past!
4
+
5
+ ![CleanShot 2022-03-12 at 23 41 03](https://user-images.githubusercontent.com/7149034/158014859-47625c95-aa59-489a-9681-eb36e378dcba.gif)
6
+
7
+ Inspired by the Go playground and SQL Server Management Studio (SSMS) Query Editor, `playground` is a rails engine that makes it really easy to experiment with your application domain.
8
+
9
+ In addition to a web console, `playground` comes with a script management feature to help your personal and team development. It allows you to:
10
+ * use and save gitignored scripts for personal use.
11
+ * use and save version-controller scripts with your team.
12
+
13
+ This way, shared scripts stay close to the code and more maintainable while your personal script do not pollute the git history.
14
+
15
+ ## Usage
16
+
17
+ There are three ways to run ruby code in the console:
18
+
19
+ * Select your code in the editor and press cmd+Enter
20
+ * Put your cursor on any line (no selection) and press cmd+Enter
21
+ * Type your code straight in the terminal box just like in a rails console
22
+
23
+ ## Installation
24
+
25
+ Playground is built on top of `web-console` gem. Just like `web-console`, `playground` is only meant to be used in development. Every `web-console` information still holds true for `playground`. Check `web-console` [repository for more information](https://github.com/rails/web-console)
26
+
27
+
28
+ You can require `playground` after `web-console` in your Gemfile if you already use it.
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ group :development do
33
+ gem "playground"
34
+ end
35
+ ```
36
+
37
+ And then execute:
38
+ ```bash
39
+ $ bundle
40
+ ```
41
+
42
+ Then mount `playground` in your routes file and access the playground at `http://localhost:{PORT}/playground` on your local machine.
43
+ ```ruby
44
+ Rails.application.routes.draw do
45
+ mount Playground::Engine => "/playground"
46
+ # ...
47
+ end
48
+ ```
49
+
50
+ ## Troubleshouting
51
+
52
+ ### Session :id is no longer available in memory.
53
+
54
+ Example:
55
+ ```
56
+ Session e96248aec85b3006488c276b9468a4d8 is no longer available in memory.
57
+
58
+ If you happen to run on a multi-process server (like Unicorn or Puma) the process
59
+ this request hit doesn't store e96248aec85b3006488c276b9468a4d8 in memory. Consider turning the number of
60
+ processes/workers to one (1) or using a different server in development.
61
+ ```
62
+
63
+ This is a known `web-console` error which happened when you run a multi threaded server locally.
64
+ To solve this problem make sure to set the number of processes/worker to 1 on your local server.
65
+
66
+ ## License
67
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
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"
@@ -0,0 +1,17 @@
1
+ import "@hotwired/turbo-rails"
2
+ import "@rails/request.js"
3
+ import { Application } from "@hotwired/stimulus"
4
+
5
+ import HelloController from "./controllers/hello_controller"
6
+ import PlaygroundController from "./controllers/playground_controller"
7
+ import SearchScriptsController from "./controllers/search_scripts_controller"
8
+ import SelectScriptController from "./controllers/select_script_controller"
9
+ import CodemirrorController from "./controllers/codemirror_controller"
10
+
11
+ window.Stimulus = Application.start()
12
+
13
+ Stimulus.register("hello", HelloController)
14
+ Stimulus.register("playground", PlaygroundController)
15
+ Stimulus.register("search-scripts", SearchScriptsController)
16
+ Stimulus.register("select-script", SelectScriptController)
17
+ Stimulus.register("codemirror", CodemirrorController)
@@ -0,0 +1,49 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { EditorView, keymap } from "@codemirror/view"
3
+ import { EditorState, Prec } from "@codemirror/state"
4
+ import { basicSetup } from "@codemirror/basic-setup"
5
+ import { indentWithTab } from "@codemirror/commands"
6
+ import { StreamLanguage } from "@codemirror/stream-parser"
7
+ import { ruby } from "@codemirror/legacy-modes/mode/ruby"
8
+
9
+ export default class extends Controller {
10
+ static values = { script: String }
11
+
12
+ connect() {
13
+ this.editor = new EditorView({
14
+ parent: this.element,
15
+ state: EditorState.create({
16
+ doc: this.scriptValue,
17
+ extensions: [
18
+ basicSetup,
19
+ StreamLanguage.define(ruby),
20
+ Prec.high(keymap.of([{key: "Mod-Enter", run: () => this.submitCodeSelected}])),
21
+ keymap.of([indentWithTab])
22
+ ]
23
+ })
24
+ })
25
+ }
26
+
27
+ get submitCodeSelected() {
28
+ const event = new CustomEvent('codeSubmission', { detail: { selection: this.selectedBlock } });
29
+ this.element.dispatchEvent(event);
30
+ return true
31
+ }
32
+
33
+ get selection() {
34
+ return this.editor.state.selection.main
35
+ }
36
+
37
+ get doc() {
38
+ return this.editor.state.doc
39
+ }
40
+
41
+ get selectedBlock() {
42
+ const { from, to } = this.selection
43
+ if (from != to) {
44
+ return this.doc.toString().slice(from, to)
45
+ } else {
46
+ return this.doc.lineAt(from).text
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,7 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ console.log("Hello World!", this.element)
6
+ }
7
+ }
@@ -0,0 +1,12 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+
3
+ export default class extends Controller {
4
+ connect() {
5
+ this.console = REPLConsole.installInto('console')
6
+ }
7
+
8
+ runCode(event) {
9
+ this.console.setInput(event.detail.selection)
10
+ this.console.onEnterKey()
11
+ }
12
+ }
@@ -0,0 +1,20 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { get } from "@rails/request.js"
3
+
4
+ export default class extends Controller {
5
+ static targets = ['input']
6
+ static values = { url: String }
7
+
8
+ debounce(callback, time) {
9
+ window.clearTimeout(this.debounceTimer);
10
+ this.debounceTimer = window.setTimeout(callback, time);
11
+ }
12
+
13
+ getScripts(event) {
14
+ let callback = () => get(this.urlValue, {
15
+ query: { name: event.target.value }
16
+ })
17
+
18
+ this.debounce(callback, 500)
19
+ }
20
+ }
@@ -0,0 +1,8 @@
1
+ import { Controller } from "@hotwired/stimulus"
2
+ import { get } from "@rails/request.js"
3
+
4
+ export default class extends Controller {
5
+ getScript(event) {
6
+ get(event.target.value)
7
+ }
8
+ }
@@ -0,0 +1,28 @@
1
+ body#playground { max-width:900px; margin:auto; padding:0 1rem;
2
+ .console { margin-top:1rem;
3
+ position: relative; border: .5rem solid black;
4
+ .console-outer {
5
+ position:unset;
6
+ }
7
+ }
8
+
9
+ .console-actions { display: none;}
10
+
11
+ .code { flex-grow:1; height:550px; position: relative; }
12
+ .sidebar { margin-right:1rem;}
13
+
14
+ input, select, .console {
15
+ -webkit-box-sizing:border-box;
16
+ -moz-box-sizing:border-box;
17
+ box-sizing:border-box;
18
+ }
19
+
20
+ select { font-size:0.7em; padding:1rem; height:100%; max-height:500px; }
21
+ input { margin-bottom:0.5rem; padding:0.5rem;}
22
+ select, input { width:200px; }
23
+
24
+ [data-controller="codemirror"] { height:100%;
25
+ .cm-scroller { overflow: auto }
26
+ .cm-editor { position:absolute !important; top:0; right:0; bottom:0; left:0; }
27
+ }
28
+ }
@@ -0,0 +1,4 @@
1
+ module Playground
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,7 @@
1
+ module Playground
2
+ class ConsolesController < ApplicationController
3
+ def show
4
+ console
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ module Playground
2
+ class ScriptsController < ApplicationController
3
+ def index
4
+ @scripts = script_paths
5
+
6
+ if params[:name]
7
+ @scripts = @scripts.select { |script| script.downcase.match? params[:name].downcase }
8
+ end
9
+ end
10
+
11
+ def show
12
+ @_script = File.read script_path(params[:name])
13
+ end
14
+
15
+ def new
16
+ @_script = <<~SCRIPT
17
+ # Access your application domain as if you were in a rails console.
18
+ # There are three ways to run code in the console:
19
+
20
+ # 1 - Select all the code from line 5 to 11 and press cmd+Enter
21
+ module HelloWorld
22
+ module_function
23
+
24
+ def hello
25
+ "Hello, World!"
26
+ end
27
+ end
28
+
29
+ # 2 - Put your cursor on line 14 (no selection) and press cmd+Enter
30
+ HelloWorld.hello
31
+
32
+ # 3 - Type "HelloWorld.hello" in the terminal box below and press cmd+Enter
33
+ SCRIPT
34
+
35
+ render :show
36
+ end
37
+
38
+ private
39
+
40
+ def script_path(name)
41
+ script_paths.find { |path| path == name }
42
+ end
43
+
44
+ def script_paths
45
+ Dir[Rails.root.join("lib", "playground", "**", "*")]
46
+ .reject {|fn| File.directory?(fn) }
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,4 @@
1
+ module Playground
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Playground
2
+ module ConsolesHelper
3
+ end
4
+ end
@@ -0,0 +1,34 @@
1
+ module Playground
2
+ module ScriptsHelper
3
+ MAX_SELECT_BOX_SIZE = 15
4
+
5
+ def script_size(scripts)
6
+ [MAX_SELECT_BOX_SIZE, scripts.length + 1].min
7
+ end
8
+
9
+ def script_options(scripts)
10
+ options = scripts
11
+ .map { |path| [script_label(path), url(path)] }
12
+ .unshift(["New script", new_script_path(format: :turbo_stream)])
13
+
14
+ options_for_select options
15
+ end
16
+
17
+ private
18
+
19
+ def script_label(path)
20
+ Pathname(path)
21
+ .basename
22
+ .to_s
23
+ .truncate(30)
24
+ end
25
+
26
+ def script_pathname
27
+ Rails.root.join("lib").to_s
28
+ end
29
+
30
+ def url(path)
31
+ script_scripts_path(name: path, format: :turbo_stream)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,4 @@
1
+ module Playground
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Playground
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Playground
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Playground</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= javascript_include_tag "playground/application", skip_pipeline: true, nonce: true, media: "all" %>
9
+ <%= stylesheet_link_tag "/stylesheets/playground/application", media: "all" %>
10
+ </head>
11
+ <body id="playground">
12
+ <%= yield %>
13
+ </body>
14
+ </html>
@@ -0,0 +1,20 @@
1
+ <h1 data-controller="hello">Playground</h1>
2
+
3
+ <div style="display:flex;">
4
+ <div class="sidebar">
5
+ <div
6
+ data-controller="search-scripts"
7
+ data-search-scripts-url-value="<%= scripts_path(format: :turbo_stream) %>">
8
+ <%= text_field_tag :search, "",
9
+ placeholder: 'search for scripts',
10
+ data: { action: 'input->search-scripts#getScripts' }
11
+ %>
12
+ </div>
13
+
14
+ <%= turbo_frame_tag :scripts, src: scripts_path(format: :turbo_stream) %>
15
+ </div>
16
+
17
+ <div class="code" data-controller="playground">
18
+ <%= turbo_frame_tag :script, src: new_script_path(format: :turbo_stream) %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,5 @@
1
+ <div
2
+ data-controller="codemirror"
3
+ data-codemirror-script-value="<%= @_script %>"
4
+ data-action="codeSubmission->playground#runCode">
5
+ </div>
@@ -0,0 +1,3 @@
1
+ <div id="scripts" data-controller="select-script">
2
+ <%= select_tag :scripts, script_options(scripts) , size: script_size(scripts), data: {action: 'change->select-script#getScript'} %>
3
+ </div>
@@ -0,0 +1 @@
1
+ <%= render partial: 'script_select_box', locals: { scripts: @scripts } %>
@@ -0,0 +1 @@
1
+ <%= turbo_stream.update :scripts, partial: 'script_select_box', locals: { scripts: @scripts } %>
@@ -0,0 +1 @@
1
+ <%= render partial: 'script', locals: { script: @_script } %>
@@ -0,0 +1 @@
1
+ <%= turbo_stream.update :script, partial: 'script', locals: { script: @_script } %>
data/config/routes.rb ADDED
@@ -0,0 +1,6 @@
1
+ Playground::Engine.routes.draw do
2
+ root 'consoles#show'
3
+ resources :scripts, only: [:index, :new] do
4
+ get :script, on: :collection, to: "scripts#show"
5
+ end
6
+ end
@@ -0,0 +1,14 @@
1
+ module Playground
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Playground
4
+
5
+ initializer "playground.web_console_templates" do |app|
6
+ template_paths = File.expand_path("../templates", __FILE__)
7
+ WebConsole::Template.template_paths.unshift(*Array(template_paths))
8
+ end
9
+
10
+ initializer "playground.assets" do |app|
11
+ app.middleware.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/vendor")
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ <% if playground_page? %>
2
+
3
+ <%= render 'markup' %>
4
+
5
+ <%= render_javascript 'console' %>
6
+
7
+ <% else %>
8
+
9
+ <%= render 'markup' %>
10
+
11
+ <%= render_javascript 'console' %>
12
+ <%= render_javascript 'main' %>
13
+
14
+ <% only_on_error_page do %>
15
+ <%= render_javascript 'error_page' %>
16
+ <% end %>
17
+
18
+ <% only_on_regular_page do %>
19
+ <%= render_javascript 'regular_page' %>
20
+ <% end %>
21
+
22
+ <% end %>
@@ -0,0 +1,3 @@
1
+ module Playground
2
+ VERSION = "0.1.0"
3
+ end
data/lib/playground.rb ADDED
@@ -0,0 +1,20 @@
1
+ require "web-console"
2
+
3
+ WebConsole::View.class_eval do
4
+ def playground_page?
5
+ @playground_page ||= begin
6
+ # {:controller=>"playground/consoles", :action=>"show"}
7
+ path_parameters = @env["action_dispatch.request.path_parameters"]
8
+ path_parameters[:controller] == "playground/consoles" &&
9
+ path_parameters[:action] == "show"
10
+ end
11
+ end
12
+ end
13
+
14
+ require "turbo-rails"
15
+ require "playground/version"
16
+ require "playground/engine"
17
+
18
+ module Playground
19
+
20
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :playground do
3
+ # # Task goes here
4
+ # end