rubymonolith 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -4
- data/Rakefile +2 -0
- data/app/assets/images/monolith/logo.svg +6 -0
- data/app/assets/stylesheets/monolith/application.tailwind.css +14 -0
- data/app/components/monolith/base.rb +6 -0
- data/app/components/monolith/table.rb +37 -0
- data/app/controllers/monolith/application_controller.rb +68 -2
- data/app/controllers/monolith/emails_controller.rb +61 -0
- data/app/controllers/monolith/exceptions_controller.rb +370 -0
- data/app/controllers/monolith/gems_controller.rb +230 -0
- data/app/controllers/monolith/generators_controller.rb +377 -0
- data/app/controllers/monolith/home_controller.rb +10 -0
- data/app/controllers/monolith/models_controller.rb +319 -0
- data/app/controllers/monolith/routes_controller.rb +157 -0
- data/app/controllers/monolith/tables_controller.rb +168 -0
- data/app/views/monolith/base.rb +3 -0
- data/app/views/monolith/layouts/base.rb +23 -0
- data/config/routes.rb +14 -0
- data/lib/generators/monolith/content/USAGE +8 -0
- data/lib/generators/monolith/content/content_generator.rb +25 -0
- data/lib/generators/monolith/generators/base.rb +38 -0
- data/lib/generators/monolith/install/install_generator.rb +14 -95
- data/lib/generators/monolith/view/USAGE +8 -0
- data/lib/generators/monolith/view/view_generator.rb +20 -0
- data/lib/monolith/cli/template.rb +78 -5
- data/lib/monolith/cli.rb +22 -3
- data/lib/monolith/engine.rb +31 -0
- data/lib/monolith/version.rb +1 -1
- data/lib/tasks/monolith_tasks.rake +13 -4
- metadata +66 -10
- data/app/assets/stylesheets/monolith/application.css +0 -15
- data/app/views/layouts/monolith/application.html.erb +0 -15
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# app/controllers/monolith/gems_controller.rb
|
|
2
|
+
module Monolith
|
|
3
|
+
class GemsController < Monolith::ApplicationController
|
|
4
|
+
def index
|
|
5
|
+
render Index.new.tap { _1.gems = GemInfo.all }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
gem_info = GemInfo.find(params[:id].to_s)
|
|
10
|
+
return render plain: "Gem not found", status: :not_found unless gem_info
|
|
11
|
+
|
|
12
|
+
render Show.new.tap { |v| v.gem_info = gem_info }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# =======================
|
|
16
|
+
# Inline ActiveModel-like object
|
|
17
|
+
# =======================
|
|
18
|
+
class GemInfo
|
|
19
|
+
include Comparable
|
|
20
|
+
attr_reader :name, :version, :summary, :licenses, :homepage, :source_code_uri,
|
|
21
|
+
:bug_tracker_uri, :changelog_uri, :rubygems_uri, :path, :source_type, :source_info
|
|
22
|
+
|
|
23
|
+
def self.all
|
|
24
|
+
# Bundler.load.specs → Gem::Specification for everything in the bundle
|
|
25
|
+
specs = Bundler.load.specs.sort_by { |s| s.name }
|
|
26
|
+
specs.map { |spec| from_spec(spec) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.find(name)
|
|
30
|
+
spec = Bundler.load.specs.find { |s| s.name == name }
|
|
31
|
+
spec && from_spec(spec)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.from_spec(spec)
|
|
35
|
+
meta = spec.respond_to?(:metadata) ? (spec.metadata || {}) : {}
|
|
36
|
+
|
|
37
|
+
licenses =
|
|
38
|
+
if spec.respond_to?(:licenses) && spec.licenses.any?
|
|
39
|
+
spec.licenses
|
|
40
|
+
elsif spec.respond_to?(:license) && spec.license
|
|
41
|
+
[spec.license]
|
|
42
|
+
else
|
|
43
|
+
Array(meta["license"])
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
homepage = presence(spec.homepage) || meta["homepage_uri"]
|
|
47
|
+
source = meta["source_code_uri"]
|
|
48
|
+
bugs = meta["bug_tracker_uri"]
|
|
49
|
+
change = meta["changelog_uri"]
|
|
50
|
+
|
|
51
|
+
# Detect gem source
|
|
52
|
+
source_type, source_info = detect_source(spec)
|
|
53
|
+
|
|
54
|
+
new(
|
|
55
|
+
name: spec.name,
|
|
56
|
+
version: spec.version.to_s,
|
|
57
|
+
summary: spec.summary,
|
|
58
|
+
licenses: licenses.compact.map(&:to_s).uniq,
|
|
59
|
+
homepage: homepage,
|
|
60
|
+
source_code_uri: source,
|
|
61
|
+
bug_tracker_uri: bugs,
|
|
62
|
+
changelog_uri: change,
|
|
63
|
+
rubygems_uri: "https://rubygems.org/gems/#{spec.name}",
|
|
64
|
+
path: spec.full_gem_path,
|
|
65
|
+
source_type: source_type,
|
|
66
|
+
source_info: source_info
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def initialize(name:, version:, summary:, licenses:, homepage:, source_code_uri:, bug_tracker_uri:, changelog_uri:, rubygems_uri:, path:, source_type:, source_info:)
|
|
71
|
+
@name, @version, @summary, @licenses, @homepage, @source_code_uri, @bug_tracker_uri,
|
|
72
|
+
@changelog_uri, @rubygems_uri, @path, @source_type, @source_info =
|
|
73
|
+
name, version, summary, licenses, homepage, source_code_uri, bug_tracker_uri, changelog_uri, rubygems_uri, path, source_type, source_info
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_param = name
|
|
77
|
+
def <=>(other) = name <=> other.name
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def self.presence(str)
|
|
82
|
+
s = str.to_s.strip
|
|
83
|
+
s.empty? ? nil : s
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def self.detect_source(spec)
|
|
87
|
+
# Check Bundler's locked specs to find the source
|
|
88
|
+
locked_spec = Bundler.locked_gems&.specs&.find { |s| s.name == spec.name }
|
|
89
|
+
|
|
90
|
+
if locked_spec && locked_spec.source
|
|
91
|
+
case locked_spec.source
|
|
92
|
+
when Bundler::Source::Rubygems
|
|
93
|
+
[:rubygems, "https://rubygems.org/gems/#{spec.name}"]
|
|
94
|
+
when Bundler::Source::Git
|
|
95
|
+
git_uri = locked_spec.source.uri
|
|
96
|
+
git_ref = locked_spec.source.ref || locked_spec.source.branch || locked_spec.source.revision
|
|
97
|
+
if git_uri.to_s =~ /github\.com/
|
|
98
|
+
[:github, "#{git_uri} @ #{git_ref}"]
|
|
99
|
+
else
|
|
100
|
+
[:git, "#{git_uri} @ #{git_ref}"]
|
|
101
|
+
end
|
|
102
|
+
when Bundler::Source::Path
|
|
103
|
+
[:path, locked_spec.source.path.to_s]
|
|
104
|
+
else
|
|
105
|
+
[:unknown, locked_spec.source.class.name]
|
|
106
|
+
end
|
|
107
|
+
else
|
|
108
|
+
# Fallback: check if path looks like it's from RubyGems cache
|
|
109
|
+
if spec.full_gem_path.to_s.include?('/gems/')
|
|
110
|
+
[:rubygems, "https://rubygems.org/gems/#{spec.name}"]
|
|
111
|
+
elsif spec.full_gem_path.to_s.include?('/bundler/gems/')
|
|
112
|
+
[:git, spec.full_gem_path.to_s]
|
|
113
|
+
else
|
|
114
|
+
[:path, spec.full_gem_path.to_s]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
rescue => e
|
|
118
|
+
[:unknown, "Error: #{e.message}"]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
class View < View
|
|
123
|
+
|
|
124
|
+
protected
|
|
125
|
+
|
|
126
|
+
def gem_origin_link(g)
|
|
127
|
+
case g.source_type
|
|
128
|
+
when :rubygems
|
|
129
|
+
ext_link g.rubygems_uri
|
|
130
|
+
when :github
|
|
131
|
+
git_uri = g.source_info.split(' @ ').first
|
|
132
|
+
git_ref = g.source_info.split(' @ ').last
|
|
133
|
+
# Convert GitHub URI to branch/commit URL
|
|
134
|
+
normalized_uri = git_uri.to_s.sub(/\.git$/, '')
|
|
135
|
+
branch_url = "#{normalized_uri}/tree/#{git_ref}"
|
|
136
|
+
ext_link branch_url
|
|
137
|
+
when :git
|
|
138
|
+
code { g.source_info }
|
|
139
|
+
when :path
|
|
140
|
+
if defined?(ActiveSupport::Editor) && (editor = ActiveSupport::Editor.current)
|
|
141
|
+
# Try to link to the path in the editor
|
|
142
|
+
absolute_path = File.expand_path(g.source_info)
|
|
143
|
+
if absolute_path && File.exist?(absolute_path)
|
|
144
|
+
ext_link editor.url_for(absolute_path, 1), "Local Path: #{g.source_info}"
|
|
145
|
+
else
|
|
146
|
+
code { g.source_info }
|
|
147
|
+
end
|
|
148
|
+
else
|
|
149
|
+
code { g.source_info }
|
|
150
|
+
end
|
|
151
|
+
else
|
|
152
|
+
code { "#{g.source_type}: #{g.source_info}" }
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# =======================
|
|
158
|
+
# Phlex views
|
|
159
|
+
# =======================
|
|
160
|
+
#
|
|
161
|
+
class Index < View
|
|
162
|
+
attr_writer :gems
|
|
163
|
+
|
|
164
|
+
def view_template
|
|
165
|
+
div(class: "p-6 space-y-4") do
|
|
166
|
+
h1(class: "text-2xl font-bold") { "Gems" }
|
|
167
|
+
p(class: "text-sm") { "From your current Bundler context (Gemfile.lock)." }
|
|
168
|
+
|
|
169
|
+
Table @gems do
|
|
170
|
+
it.row("Gem") {
|
|
171
|
+
nav_link it.name, controller: "/monolith/gems", action: :show, id: it.name
|
|
172
|
+
}
|
|
173
|
+
it.row("Version") {
|
|
174
|
+
it.version
|
|
175
|
+
}
|
|
176
|
+
it.row("Licenses") {
|
|
177
|
+
it.licenses.any? ? it.licenses.join(", ") : em { "—" }
|
|
178
|
+
}
|
|
179
|
+
it.row("Origin") {
|
|
180
|
+
gem_origin_link it
|
|
181
|
+
}
|
|
182
|
+
it.row("Description") {
|
|
183
|
+
it.summary.to_s.strip.empty? ? em { "—" } : it.summary
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
class Show < View
|
|
191
|
+
attr_writer :gem_info
|
|
192
|
+
|
|
193
|
+
def view_template
|
|
194
|
+
g = @gem_info
|
|
195
|
+
|
|
196
|
+
div(class: "p-6 space-y-4") do
|
|
197
|
+
h1(class: "text-2xl font-bold") { "#{g.name} (#{g.version})" }
|
|
198
|
+
p { g.summary.to_s.strip.empty? ? em { "No summary" } : g.summary }
|
|
199
|
+
|
|
200
|
+
dl(class: "grid grid-cols-1 md:grid-cols-2 gap-x-8 gap-y-2") do
|
|
201
|
+
dt(class: "font-semibold") { "Licenses" }
|
|
202
|
+
dd { g.licenses.any? ? g.licenses.join(", ") : em { "—" } }
|
|
203
|
+
|
|
204
|
+
dt(class: "font-semibold") { "Homepage" }
|
|
205
|
+
dd { ext_link g.homepage, g.homepage&.sub(%r{\Ahttps?://}, "") }
|
|
206
|
+
|
|
207
|
+
dt(class: "font-semibold") { "Source" }
|
|
208
|
+
dd { ext_link g.source_code_uri, g.source_code_uri&.sub(%r{\Ahttps?://}, "") }
|
|
209
|
+
|
|
210
|
+
dt(class: "font-semibold") { "Bugs" }
|
|
211
|
+
dd { ext_link g.bug_tracker_uri, g.bug_tracker_uri&.sub(%r{\Ahttps?://}, "") }
|
|
212
|
+
|
|
213
|
+
dt(class: "font-semibold") { "Changelog" }
|
|
214
|
+
dd { ext_link g.changelog_uri, g.changelog_uri&.sub(%r{\Ahttps?://}, "") }
|
|
215
|
+
|
|
216
|
+
dt(class: "font-semibold") { "Origin" }
|
|
217
|
+
dd {
|
|
218
|
+
gem_origin_link g
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
dt(class: "font-semibold") { "Path" }
|
|
222
|
+
dd { code { g.path } }
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
div(class: "pt-4") { nav_link "← All gems", controller: "/monolith/gems", action: :index }
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
# app/controllers/monolith/generators_controller.rb
|
|
2
|
+
module Monolith
|
|
3
|
+
class GeneratorsController < Monolith::ApplicationController
|
|
4
|
+
def index
|
|
5
|
+
render Index.new.tap { _1.generators = Generator.all }
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def show
|
|
9
|
+
generator = Generator.find(params[:id].to_s)
|
|
10
|
+
return render plain: "Generator not found", status: :not_found unless generator
|
|
11
|
+
|
|
12
|
+
render Show.new.tap { |v| v.generator = generator }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
generator = Generator.find(params[:id].to_s)
|
|
17
|
+
return render plain: "Generator not found", status: :not_found unless generator
|
|
18
|
+
|
|
19
|
+
args = params[:args].to_s.split
|
|
20
|
+
options = params[:options].to_h.reject { |k, v| v.blank? }
|
|
21
|
+
|
|
22
|
+
result = generator.invoke(args, options)
|
|
23
|
+
|
|
24
|
+
render Create.new.tap { |v|
|
|
25
|
+
v.generator = generator
|
|
26
|
+
v.result = result
|
|
27
|
+
v.args = args
|
|
28
|
+
v.options = options
|
|
29
|
+
}
|
|
30
|
+
rescue => e
|
|
31
|
+
render plain: "Error: #{e.message}\n\n#{e.backtrace.join("\n")}", status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# =======================
|
|
35
|
+
# Inline ActiveModel-like object
|
|
36
|
+
# =======================
|
|
37
|
+
class Generator
|
|
38
|
+
attr_reader :name, :namespace, :klass
|
|
39
|
+
|
|
40
|
+
def self.all
|
|
41
|
+
Rails.application.load_generators
|
|
42
|
+
|
|
43
|
+
# Get all generator namespaces
|
|
44
|
+
namespaces = Rails::Generators.hidden_namespaces + Rails::Generators.public_namespaces
|
|
45
|
+
namespaces = namespaces.uniq.sort
|
|
46
|
+
|
|
47
|
+
# Find and instantiate each generator
|
|
48
|
+
generators = namespaces.map do |namespace|
|
|
49
|
+
begin
|
|
50
|
+
klass = Rails::Generators.find_by_namespace(namespace)
|
|
51
|
+
klass && !klass.name.nil? ? new(klass) : nil
|
|
52
|
+
rescue
|
|
53
|
+
nil
|
|
54
|
+
end
|
|
55
|
+
end.compact
|
|
56
|
+
|
|
57
|
+
generators
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.find(namespace)
|
|
61
|
+
Rails.application.load_generators
|
|
62
|
+
|
|
63
|
+
klass = Rails::Generators.find_by_namespace(namespace)
|
|
64
|
+
klass && new(klass)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def initialize(klass)
|
|
68
|
+
@klass = klass
|
|
69
|
+
@namespace = klass.namespace
|
|
70
|
+
@name = @namespace.split(':').last
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def to_param
|
|
74
|
+
namespace
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def description
|
|
78
|
+
klass.desc rescue nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def arguments
|
|
82
|
+
return [] unless klass.respond_to?(:arguments)
|
|
83
|
+
|
|
84
|
+
klass.arguments.map do |arg|
|
|
85
|
+
{
|
|
86
|
+
name: arg.name,
|
|
87
|
+
type: arg.type,
|
|
88
|
+
required: arg.required?,
|
|
89
|
+
description: arg.description,
|
|
90
|
+
default: arg.default
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
rescue
|
|
94
|
+
[]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def class_options
|
|
98
|
+
return {} unless klass.respond_to?(:class_options)
|
|
99
|
+
|
|
100
|
+
klass.class_options.transform_values do |option|
|
|
101
|
+
{
|
|
102
|
+
type: option.type,
|
|
103
|
+
default: option.default,
|
|
104
|
+
description: option.description,
|
|
105
|
+
aliases: option.aliases,
|
|
106
|
+
banner: option.banner
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
rescue
|
|
110
|
+
{}
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def source_location
|
|
114
|
+
return nil unless klass.respond_to?(:instance_methods)
|
|
115
|
+
|
|
116
|
+
method = klass.instance_method(:initialize) rescue nil
|
|
117
|
+
return nil unless method
|
|
118
|
+
|
|
119
|
+
file, line = method.source_location
|
|
120
|
+
file
|
|
121
|
+
rescue
|
|
122
|
+
nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def invoke(args, options = {})
|
|
126
|
+
require 'stringio'
|
|
127
|
+
|
|
128
|
+
# Capture output
|
|
129
|
+
output = StringIO.new
|
|
130
|
+
original_stdout = $stdout
|
|
131
|
+
original_stderr = $stderr
|
|
132
|
+
$stdout = output
|
|
133
|
+
$stderr = output
|
|
134
|
+
|
|
135
|
+
begin
|
|
136
|
+
# Convert options hash to array format Thor expects
|
|
137
|
+
thor_args = args.dup
|
|
138
|
+
options.each do |key, value|
|
|
139
|
+
next if value.blank?
|
|
140
|
+
thor_args << "--#{key}=#{value}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Invoke the generator
|
|
144
|
+
Rails::Generators.invoke(namespace, thor_args, behavior: :invoke, destination_root: Rails.root)
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
success: true,
|
|
148
|
+
output: output.string,
|
|
149
|
+
error: nil
|
|
150
|
+
}
|
|
151
|
+
rescue => e
|
|
152
|
+
{
|
|
153
|
+
success: false,
|
|
154
|
+
output: output.string,
|
|
155
|
+
error: "#{e.class}: #{e.message}"
|
|
156
|
+
}
|
|
157
|
+
ensure
|
|
158
|
+
$stdout = original_stdout
|
|
159
|
+
$stderr = original_stderr
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# =======================
|
|
165
|
+
# Phlex views
|
|
166
|
+
# =======================
|
|
167
|
+
class Index < View
|
|
168
|
+
attr_writer :generators
|
|
169
|
+
|
|
170
|
+
def view_template
|
|
171
|
+
div(class: "p-6 space-y-4") do
|
|
172
|
+
h1(class: "text-2xl font-bold") { "Generators" }
|
|
173
|
+
p(class: "text-sm") { "#{@generators.size} Rails generators available." }
|
|
174
|
+
|
|
175
|
+
Table @generators do
|
|
176
|
+
it.row("Generator") {
|
|
177
|
+
nav_link it.name, controller: "/monolith/generators", action: :show, id: it.to_param
|
|
178
|
+
}
|
|
179
|
+
it.row("Namespace") { |g|
|
|
180
|
+
code { g.namespace }
|
|
181
|
+
}
|
|
182
|
+
it.row("Description") {
|
|
183
|
+
it.description || em { "—" }
|
|
184
|
+
}
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
class Show < View
|
|
191
|
+
attr_writer :generator
|
|
192
|
+
|
|
193
|
+
def view_template
|
|
194
|
+
g = @generator
|
|
195
|
+
|
|
196
|
+
div(class: "p-6 space-y-6") do
|
|
197
|
+
h1(class: "text-2xl font-bold") { g.name }
|
|
198
|
+
p { code { "rails generate #{g.namespace}" } }
|
|
199
|
+
|
|
200
|
+
if g.description
|
|
201
|
+
p { g.description }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Arguments
|
|
205
|
+
section do
|
|
206
|
+
h2(class: "text-xl font-bold mb-2") { "Arguments" }
|
|
207
|
+
if g.arguments.any?
|
|
208
|
+
ul(class: "list-disc pl-6 space-y-1") do
|
|
209
|
+
g.arguments.each do |arg|
|
|
210
|
+
li do
|
|
211
|
+
code { arg[:name] }
|
|
212
|
+
plain " (#{arg[:type]}"
|
|
213
|
+
plain ", required" if arg[:required]
|
|
214
|
+
plain ")"
|
|
215
|
+
if arg[:description]
|
|
216
|
+
plain " - #{arg[:description]}"
|
|
217
|
+
end
|
|
218
|
+
if arg[:default]
|
|
219
|
+
plain " [default: #{arg[:default]}]"
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
else
|
|
225
|
+
p { em { "No arguments" } }
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Options
|
|
230
|
+
section do
|
|
231
|
+
h2(class: "text-xl font-bold mb-2") { "Options" }
|
|
232
|
+
if g.class_options.any?
|
|
233
|
+
ul(class: "list-disc pl-6 space-y-1") do
|
|
234
|
+
g.class_options.each do |name, option|
|
|
235
|
+
li do
|
|
236
|
+
code { "--#{name}" }
|
|
237
|
+
if option[:aliases]&.any?
|
|
238
|
+
plain " ("
|
|
239
|
+
code { option[:aliases].join(", ") }
|
|
240
|
+
plain ")"
|
|
241
|
+
end
|
|
242
|
+
plain " - #{option[:type]}"
|
|
243
|
+
if option[:description]
|
|
244
|
+
plain " - #{option[:description]}"
|
|
245
|
+
end
|
|
246
|
+
if option[:default]
|
|
247
|
+
plain " [default: #{option[:default]}]"
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
p { em { "No options" } }
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Invoke Form
|
|
258
|
+
section do
|
|
259
|
+
h2(class: "text-xl font-bold mb-2") { "Run Generator" }
|
|
260
|
+
form(method: "post", action: url_for(controller: "/monolith/generators", action: :create, id: g.to_param)) do
|
|
261
|
+
div(class: "space-y-3") do
|
|
262
|
+
# CSRF token
|
|
263
|
+
input(type: "hidden", name: "authenticity_token", value: form_authenticity_token)
|
|
264
|
+
|
|
265
|
+
# Arguments input
|
|
266
|
+
div do
|
|
267
|
+
label(class: "block font-semibold mb-1") { "Arguments (space-separated)" }
|
|
268
|
+
input(
|
|
269
|
+
type: "text",
|
|
270
|
+
name: "args",
|
|
271
|
+
placeholder: g.arguments.map { |a| a[:name] }.join(" "),
|
|
272
|
+
class: "w-full border px-3 py-2"
|
|
273
|
+
)
|
|
274
|
+
if g.arguments.any?
|
|
275
|
+
p(class: "text-sm mt-1") do
|
|
276
|
+
plain "Expected: "
|
|
277
|
+
g.arguments.each_with_index do |arg, idx|
|
|
278
|
+
plain " " if idx > 0
|
|
279
|
+
code { arg[:name] }
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Options
|
|
286
|
+
if g.class_options.any?
|
|
287
|
+
div do
|
|
288
|
+
label(class: "block font-semibold mb-2") { "Options" }
|
|
289
|
+
div(class: "space-y-2") do
|
|
290
|
+
g.class_options.each do |name, option|
|
|
291
|
+
div(class: "flex items-center gap-2") do
|
|
292
|
+
label(class: "w-48") {
|
|
293
|
+
code { "--#{name}" }
|
|
294
|
+
}
|
|
295
|
+
case option[:type]
|
|
296
|
+
when :boolean
|
|
297
|
+
input(type: "checkbox", name: "options[#{name}]", value: "true")
|
|
298
|
+
else
|
|
299
|
+
input(
|
|
300
|
+
type: "text",
|
|
301
|
+
name: "options[#{name}]",
|
|
302
|
+
placeholder: option[:default]&.to_s || option[:banner]&.to_s,
|
|
303
|
+
class: "flex-1 border px-2 py-1"
|
|
304
|
+
)
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# Submit button
|
|
313
|
+
div do
|
|
314
|
+
button(
|
|
315
|
+
type: "submit",
|
|
316
|
+
class: "px-4 py-2 border font-semibold hover:bg-gray-100"
|
|
317
|
+
) { "Generate" }
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Source location
|
|
324
|
+
if g.source_location
|
|
325
|
+
section do
|
|
326
|
+
h2(class: "text-xl font-bold mb-2") { "Source" }
|
|
327
|
+
p { code { g.source_location } }
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
div(class: "pt-4") { nav_link "← All generators", controller: "/monolith/generators", action: :index }
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
class Create < View
|
|
337
|
+
attr_writer :generator, :result, :args, :options
|
|
338
|
+
|
|
339
|
+
def view_template
|
|
340
|
+
g = @generator
|
|
341
|
+
r = @result
|
|
342
|
+
|
|
343
|
+
div(class: "p-6 space-y-6") do
|
|
344
|
+
h1(class: "text-2xl font-bold") { "Generator Result: #{g.name}" }
|
|
345
|
+
|
|
346
|
+
if r[:success]
|
|
347
|
+
div(class: "border border-green-600 p-4") do
|
|
348
|
+
h2(class: "font-bold text-green-600 mb-2") { "✓ Success" }
|
|
349
|
+
p do
|
|
350
|
+
plain "Invoked: "
|
|
351
|
+
code { "rails generate #{g.namespace} #{@args.join(' ')}" }
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
else
|
|
355
|
+
div(class: "border border-red-600 p-4") do
|
|
356
|
+
h2(class: "font-bold text-red-600 mb-2") { "✗ Error" }
|
|
357
|
+
p { r[:error] }
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
# Output
|
|
362
|
+
section do
|
|
363
|
+
h2(class: "text-xl font-bold mb-2") { "Output" }
|
|
364
|
+
pre(class: "border p-4 overflow-x-auto text-xs") do
|
|
365
|
+
code { r[:output] }
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
div(class: "pt-4 space-x-4") do
|
|
370
|
+
nav_link "← Back to generator", controller: "/monolith/generators", action: :show, id: g.to_param
|
|
371
|
+
nav_link "All generators", controller: "/monolith/generators", action: :index
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
end
|
|
377
|
+
end
|