glib-web 4.44.4 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/helpers/glib/format_helper.rb +1 -1
- data/app/helpers/glib/json_ui/abstract_builder.rb +1 -1
- data/app/helpers/glib/json_ui/action_builder/browsers.rb +1 -3
- data/app/helpers/glib/json_ui/view_builder/fields.rb +0 -53
- data/app/views/json_ui/garage/forms/file_upload.json.jbuilder +0 -65
- data/app/views/json_ui/garage/test_page/_header.json.jbuilder +9 -2
- data/app/views/json_ui/garage/test_page/auth.json.jbuilder +37 -0
- data/app/views/json_ui/garage/test_page/browsers.json.jbuilder +1 -0
- data/app/views/json_ui/garage/test_page/custom.json.jbuilder +58 -50
- data/app/views/json_ui/garage/test_page/dialog.json.jbuilder +123 -26
- data/app/views/json_ui/garage/test_page/dirty_state.json.jbuilder +0 -5
- data/app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder +118 -0
- data/app/views/json_ui/garage/test_page/fields_date_time.json.jbuilder +213 -0
- data/app/views/json_ui/garage/test_page/fields_location.json.jbuilder +151 -0
- data/app/views/json_ui/garage/test_page/fields_otp.json.jbuilder +168 -0
- data/app/views/json_ui/garage/test_page/fields_phone.json.jbuilder +176 -0
- data/app/views/json_ui/garage/test_page/fields_rating.json.jbuilder +191 -0
- data/app/views/json_ui/garage/test_page/fields_richText.json.jbuilder +213 -0
- data/app/views/json_ui/garage/test_page/fields_stripeExternalAccount.json.jbuilder +146 -0
- data/app/views/json_ui/garage/test_page/fields_stripeToken.json.jbuilder +118 -0
- data/app/views/json_ui/garage/test_page/fields_upload.json.jbuilder +15 -1
- data/app/views/json_ui/garage/test_page/popovers.json.jbuilder +91 -16
- data/app/views/json_ui/garage/test_page/window.json.jbuilder +23 -14
- data/app/views/json_ui/garage/test_page/windows.json.jbuilder +74 -16
- data/lib/glib/rubocop/cops/multiline_method_call_style.rb +0 -8
- data/lib/tasks/db.rake +0 -1
- metadata +29 -13
- data/app/views/json_ui/garage/test_page/multiupload.json.jbuilder +0 -65
- data/lib/glib/doc_generator.rb +0 -386
metadata
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 5.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
10
|
date: 2019-10-04 00:00:00.000000000 Z
|
|
@@ -16,14 +15,14 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: 8.0
|
|
18
|
+
version: '8.0'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: 8.0
|
|
25
|
+
version: '8.0'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: pundit
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -58,14 +57,14 @@ dependencies:
|
|
|
58
57
|
requirements:
|
|
59
58
|
- - "~>"
|
|
60
59
|
- !ruby/object:Gem::Version
|
|
61
|
-
version: 8.0
|
|
60
|
+
version: '8.0'
|
|
62
61
|
type: :runtime
|
|
63
62
|
prerelease: false
|
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
65
64
|
requirements:
|
|
66
65
|
- - "~>"
|
|
67
66
|
- !ruby/object:Gem::Version
|
|
68
|
-
version: 8.0
|
|
67
|
+
version: '8.0'
|
|
69
68
|
- !ruby/object:Gem::Dependency
|
|
70
69
|
name: js_regex
|
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -108,6 +107,20 @@ dependencies:
|
|
|
108
107
|
- - ">="
|
|
109
108
|
- !ruby/object:Gem::Version
|
|
110
109
|
version: '0'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: redcarpet
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
type: :runtime
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '0'
|
|
111
124
|
- !ruby/object:Gem::Dependency
|
|
112
125
|
name: rubocop
|
|
113
126
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -136,7 +149,6 @@ dependencies:
|
|
|
136
149
|
- - ">="
|
|
137
150
|
- !ruby/object:Gem::Version
|
|
138
151
|
version: '0'
|
|
139
|
-
description:
|
|
140
152
|
email: ''
|
|
141
153
|
executables: []
|
|
142
154
|
extensions: []
|
|
@@ -389,9 +401,18 @@ files:
|
|
|
389
401
|
- app/views/json_ui/garage/test_page/dirty_state.json.jbuilder
|
|
390
402
|
- app/views/json_ui/garage/test_page/fields.json.jbuilder
|
|
391
403
|
- app/views/json_ui/garage/test_page/fields_captcha.json.jbuilder
|
|
404
|
+
- app/views/json_ui/garage/test_page/fields_creditCard.json.jbuilder
|
|
405
|
+
- app/views/json_ui/garage/test_page/fields_date_time.json.jbuilder
|
|
392
406
|
- app/views/json_ui/garage/test_page/fields_dynamicSelect.json.jbuilder
|
|
407
|
+
- app/views/json_ui/garage/test_page/fields_location.json.jbuilder
|
|
408
|
+
- app/views/json_ui/garage/test_page/fields_otp.json.jbuilder
|
|
409
|
+
- app/views/json_ui/garage/test_page/fields_phone.json.jbuilder
|
|
410
|
+
- app/views/json_ui/garage/test_page/fields_rating.json.jbuilder
|
|
411
|
+
- app/views/json_ui/garage/test_page/fields_richText.json.jbuilder
|
|
393
412
|
- app/views/json_ui/garage/test_page/fields_select.json.jbuilder
|
|
394
413
|
- app/views/json_ui/garage/test_page/fields_sign.json.jbuilder
|
|
414
|
+
- app/views/json_ui/garage/test_page/fields_stripeExternalAccount.json.jbuilder
|
|
415
|
+
- app/views/json_ui/garage/test_page/fields_stripeToken.json.jbuilder
|
|
395
416
|
- app/views/json_ui/garage/test_page/fields_timer.json.jbuilder
|
|
396
417
|
- app/views/json_ui/garage/test_page/fields_upload.json.jbuilder
|
|
397
418
|
- app/views/json_ui/garage/test_page/fields_url_fragment.json.jbuilder
|
|
@@ -409,7 +430,6 @@ files:
|
|
|
409
430
|
- app/views/json_ui/garage/test_page/lists_append.json.jbuilder
|
|
410
431
|
- app/views/json_ui/garage/test_page/logics_set.json.jbuilder
|
|
411
432
|
- app/views/json_ui/garage/test_page/multimedia_video.json.jbuilder
|
|
412
|
-
- app/views/json_ui/garage/test_page/multiupload.json.jbuilder
|
|
413
433
|
- app/views/json_ui/garage/test_page/pagination.json.jbuilder
|
|
414
434
|
- app/views/json_ui/garage/test_page/panels.json.jbuilder
|
|
415
435
|
- app/views/json_ui/garage/test_page/panels_bulkEdit2.json.jbuilder
|
|
@@ -473,7 +493,6 @@ files:
|
|
|
473
493
|
- lib/glib-web.rb
|
|
474
494
|
- lib/glib/all_helpers.rb
|
|
475
495
|
- lib/glib/crypt/utils.rb
|
|
476
|
-
- lib/glib/doc_generator.rb
|
|
477
496
|
- lib/glib/dynamic_text.rb
|
|
478
497
|
- lib/glib/dynamic_text/config.rb
|
|
479
498
|
- lib/glib/engine.rb
|
|
@@ -509,10 +528,8 @@ files:
|
|
|
509
528
|
- lib/glib/version.rb
|
|
510
529
|
- lib/tasks/db.rake
|
|
511
530
|
- lib/tasks/docs.rake
|
|
512
|
-
homepage:
|
|
513
531
|
licenses: []
|
|
514
532
|
metadata: {}
|
|
515
|
-
post_install_message:
|
|
516
533
|
rdoc_options: []
|
|
517
534
|
require_paths:
|
|
518
535
|
- lib
|
|
@@ -527,8 +544,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
527
544
|
- !ruby/object:Gem::Version
|
|
528
545
|
version: '0'
|
|
529
546
|
requirements: []
|
|
530
|
-
rubygems_version:
|
|
531
|
-
signing_key:
|
|
547
|
+
rubygems_version: 4.0.6
|
|
532
548
|
specification_version: 4
|
|
533
549
|
summary: ''
|
|
534
550
|
test_files: []
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
json.title 'Test Page (Form)'
|
|
2
|
-
|
|
3
|
-
page = json_ui_page json
|
|
4
|
-
|
|
5
|
-
page.body childViews: ->(body) do
|
|
6
|
-
render 'json_ui/garage/test_page/header', view: body
|
|
7
|
-
|
|
8
|
-
body.panels_responsive padding: glib_json_padding_body, childViews: ->(res) do
|
|
9
|
-
res.h2 text: 'MultiUpload'
|
|
10
|
-
res.spacer height: 8
|
|
11
|
-
res.panels_form \
|
|
12
|
-
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
13
|
-
method: 'post',
|
|
14
|
-
childViews: ->(form) do
|
|
15
|
-
|
|
16
|
-
form.spacer height: 16
|
|
17
|
-
form.label text: 'Simple file upload'
|
|
18
|
-
form.spacer height: 8
|
|
19
|
-
form.fields_multiUpload \
|
|
20
|
-
name: 'user[multi][]',
|
|
21
|
-
id: 'upload_1',
|
|
22
|
-
width: 360,
|
|
23
|
-
accepts: { fileType: 'image', maxFileSize: 10 },
|
|
24
|
-
directUploadUrl: glib_direct_uploads_url,
|
|
25
|
-
uploadTitle: 'Files uploaded:',
|
|
26
|
-
storagePrefix: 'glib',
|
|
27
|
-
metadata: {
|
|
28
|
-
foo: 'bar',
|
|
29
|
-
zoo: 'baz'
|
|
30
|
-
},
|
|
31
|
-
# tagging: 'key=value&key1=value1',
|
|
32
|
-
tags: { key: 'value', key1: 'value1' },
|
|
33
|
-
files: [
|
|
34
|
-
{ name: 'File (Example)', signed_id: ActiveStorage::Attachment.first&.signed_id }
|
|
35
|
-
]
|
|
36
|
-
form.button text: 'clear files', onClick: ->(action) do
|
|
37
|
-
action.components_set targetId: 'upload_1', data: { files: [], value: nil }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
form.spacer height: 16
|
|
41
|
-
form.label text: 'File upload with onFinishUpload'
|
|
42
|
-
form.spacer height: 8
|
|
43
|
-
form.fields_multiUpload \
|
|
44
|
-
name: 'user[multi2][]',
|
|
45
|
-
id: 'upload_2',
|
|
46
|
-
width: 360,
|
|
47
|
-
accepts: { fileType: 'image', maxFileSize: 5000 },
|
|
48
|
-
directUploadUrl: glib_direct_uploads_url,
|
|
49
|
-
uploadTitle: 'Files uploaded:',
|
|
50
|
-
onFinishUpload: ->(action) { action.forms_submit }
|
|
51
|
-
form.button text: 'populate files', onClick: ->(action) do
|
|
52
|
-
action.components_set targetId: 'upload_2', data: {
|
|
53
|
-
files: [
|
|
54
|
-
{ name: 'File (Example)', signed_id: ActiveStorage::Attachment.last&.signed_id }
|
|
55
|
-
],
|
|
56
|
-
value: [1]
|
|
57
|
-
}
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
form.fields_submit text: 'submit'
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
res.spacer height: 16
|
|
64
|
-
end
|
|
65
|
-
end
|
data/lib/glib/doc_generator.rb
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
require 'parser/current'
|
|
2
|
-
require 'yaml'
|
|
3
|
-
require 'fileutils'
|
|
4
|
-
require 'time'
|
|
5
|
-
|
|
6
|
-
module Glib
|
|
7
|
-
class DocGenerator
|
|
8
|
-
def initialize
|
|
9
|
-
@parser = Parser::CurrentRuby
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Generate YAML documentation for a single Ruby file
|
|
13
|
-
def generate_for_file(file_path, output_path)
|
|
14
|
-
unless File.exist?(file_path)
|
|
15
|
-
puts "Warning: File not found: #{file_path}"
|
|
16
|
-
return
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
source = File.read(file_path)
|
|
20
|
-
ast = @parser.parse(source)
|
|
21
|
-
|
|
22
|
-
components = extract_components(ast, source)
|
|
23
|
-
|
|
24
|
-
# Generate YAML
|
|
25
|
-
yaml_data = {
|
|
26
|
-
'meta' => {
|
|
27
|
-
'source_file' => file_path,
|
|
28
|
-
'generated_at' => Time.now.iso8601,
|
|
29
|
-
'generator_version' => '1.0.0'
|
|
30
|
-
},
|
|
31
|
-
'components' => components
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
# Ensure output directory exists
|
|
35
|
-
FileUtils.mkdir_p(File.dirname(output_path))
|
|
36
|
-
|
|
37
|
-
# Write YAML file
|
|
38
|
-
File.write(output_path, yaml_data.to_yaml)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
private
|
|
42
|
-
|
|
43
|
-
# Extract component information from AST
|
|
44
|
-
def extract_components(node, source)
|
|
45
|
-
components = {}
|
|
46
|
-
|
|
47
|
-
traverse_ast(node, source) do |class_node, class_name, parent_class, comment|
|
|
48
|
-
next unless class_node.type == :class
|
|
49
|
-
|
|
50
|
-
# Parse YARD comment
|
|
51
|
-
yard_doc = parse_yard_comment(comment)
|
|
52
|
-
|
|
53
|
-
# Extract properties
|
|
54
|
-
properties = extract_properties(class_node, source)
|
|
55
|
-
|
|
56
|
-
# Build component hash
|
|
57
|
-
component_key = underscore(class_name)
|
|
58
|
-
components[component_key] = {
|
|
59
|
-
'class_name' => class_name,
|
|
60
|
-
'extends' => parent_class,
|
|
61
|
-
'description' => yard_doc[:description],
|
|
62
|
-
'properties' => properties,
|
|
63
|
-
'examples' => yard_doc[:examples],
|
|
64
|
-
'references' => yard_doc[:references],
|
|
65
|
-
'notes' => yard_doc[:notes],
|
|
66
|
-
'deprecated' => yard_doc[:deprecated]
|
|
67
|
-
}.compact
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
components
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Traverse AST and yield class nodes with their comments
|
|
74
|
-
def traverse_ast(node, source, &block)
|
|
75
|
-
return unless node.is_a?(Parser::AST::Node)
|
|
76
|
-
|
|
77
|
-
if node.type == :class
|
|
78
|
-
class_name = extract_class_name(node)
|
|
79
|
-
parent_class = extract_parent_class(node)
|
|
80
|
-
comment = extract_comment(node, source)
|
|
81
|
-
|
|
82
|
-
yield node, class_name, parent_class, comment
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
# Recursively traverse child nodes
|
|
86
|
-
node.children.each do |child|
|
|
87
|
-
traverse_ast(child, source, &block)
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Extract class name from class node
|
|
92
|
-
def extract_class_name(class_node)
|
|
93
|
-
const_node = class_node.children[0]
|
|
94
|
-
if const_node.type == :const
|
|
95
|
-
const_node.children[1].to_s
|
|
96
|
-
else
|
|
97
|
-
'Unknown'
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
# Extract parent class name
|
|
102
|
-
def extract_parent_class(class_node)
|
|
103
|
-
parent_node = class_node.children[1]
|
|
104
|
-
return nil unless parent_node
|
|
105
|
-
|
|
106
|
-
if parent_node.type == :const
|
|
107
|
-
parent_node.children[1].to_s
|
|
108
|
-
else
|
|
109
|
-
nil
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# Extract comment before a node
|
|
114
|
-
def extract_comment(node, source)
|
|
115
|
-
return '' unless node.location
|
|
116
|
-
|
|
117
|
-
# Get all lines before the node
|
|
118
|
-
lines = source.lines
|
|
119
|
-
node_line = node.location.line - 1
|
|
120
|
-
|
|
121
|
-
# Walk backwards to collect comment lines
|
|
122
|
-
comment_lines = []
|
|
123
|
-
(node_line - 1).downto(0) do |i|
|
|
124
|
-
line = lines[i].strip
|
|
125
|
-
break unless line.start_with?('#') || line.empty?
|
|
126
|
-
|
|
127
|
-
if line.start_with?('#')
|
|
128
|
-
# Remove leading # and whitespace
|
|
129
|
-
comment_lines.unshift(line.sub(/^#\s?/, ''))
|
|
130
|
-
end
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
comment_lines.join("\n")
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
# Parse YARD comment into structured data
|
|
137
|
-
def parse_yard_comment(comment)
|
|
138
|
-
return {} if comment.empty?
|
|
139
|
-
|
|
140
|
-
result = {
|
|
141
|
-
description: '',
|
|
142
|
-
examples: [],
|
|
143
|
-
references: [],
|
|
144
|
-
notes: [],
|
|
145
|
-
deprecated: nil
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
current_section = :description
|
|
149
|
-
current_example = nil
|
|
150
|
-
description_lines = []
|
|
151
|
-
|
|
152
|
-
comment.lines.each do |line|
|
|
153
|
-
line = line.chomp
|
|
154
|
-
|
|
155
|
-
# Check for YARD tags
|
|
156
|
-
if line =~ /^@example\s*(.*)/
|
|
157
|
-
# Save current example if exists
|
|
158
|
-
result[:examples] << current_example if current_example
|
|
159
|
-
|
|
160
|
-
current_example = {
|
|
161
|
-
'label' => $1.strip,
|
|
162
|
-
'code' => ''
|
|
163
|
-
}
|
|
164
|
-
current_section = :example
|
|
165
|
-
elsif line =~ /^@see\s+(.*)/
|
|
166
|
-
reference = $1.strip
|
|
167
|
-
# Parse URL and description
|
|
168
|
-
if reference =~ /^(https?:\/\/\S+)\s+(.*)/
|
|
169
|
-
result[:references] << { 'url' => $1, 'description' => $2 }
|
|
170
|
-
elsif reference =~ /^(https?:\/\/\S+)/
|
|
171
|
-
result[:references] << { 'url' => $1 }
|
|
172
|
-
else
|
|
173
|
-
result[:references] << { 'text' => reference }
|
|
174
|
-
end
|
|
175
|
-
current_section = :description
|
|
176
|
-
elsif line =~ /^@note\s+(.*)/
|
|
177
|
-
result[:notes] << $1.strip
|
|
178
|
-
current_section = :description
|
|
179
|
-
elsif line =~ /^@deprecated\s*(.*)/
|
|
180
|
-
result[:deprecated] = $1.strip
|
|
181
|
-
result[:deprecated] = true if result[:deprecated].empty?
|
|
182
|
-
current_section = :description
|
|
183
|
-
else
|
|
184
|
-
# Regular content
|
|
185
|
-
if current_section == :example && current_example
|
|
186
|
-
# Remove leading spaces from example code (preserve relative indentation)
|
|
187
|
-
current_example['code'] += line + "\n"
|
|
188
|
-
elsif current_section == :description
|
|
189
|
-
description_lines << line unless line.strip.empty? && description_lines.empty?
|
|
190
|
-
end
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
# Save last example
|
|
195
|
-
result[:examples] << current_example if current_example
|
|
196
|
-
|
|
197
|
-
# Clean up example code (remove extra leading whitespace)
|
|
198
|
-
result[:examples].each do |example|
|
|
199
|
-
example['code'] = unindent(example['code'])
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
# Join description lines
|
|
203
|
-
result[:description] = description_lines.join("\n").strip
|
|
204
|
-
|
|
205
|
-
# Remove empty arrays/nils
|
|
206
|
-
result.delete(:examples) if result[:examples].empty?
|
|
207
|
-
result.delete(:references) if result[:references].empty?
|
|
208
|
-
result.delete(:notes) if result[:notes].empty?
|
|
209
|
-
result.delete(:deprecated) if result[:deprecated].nil?
|
|
210
|
-
|
|
211
|
-
result
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Extract properties from class body
|
|
215
|
-
def extract_properties(class_node, source)
|
|
216
|
-
properties = {}
|
|
217
|
-
|
|
218
|
-
class_body = class_node.children[2]
|
|
219
|
-
return properties unless class_body
|
|
220
|
-
|
|
221
|
-
traverse_class_body(class_body, source) do |method_name, args, comment|
|
|
222
|
-
# These are the DSL property definition methods
|
|
223
|
-
if [:string, :int, :bool, :float, :action, :views, :array, :hash,
|
|
224
|
-
:icon, :color, :length, :date, :date_time, :text, :any,
|
|
225
|
-
:panels_builder, :menu, :singleton_array].include?(method_name)
|
|
226
|
-
|
|
227
|
-
property_name = args.first
|
|
228
|
-
next unless property_name
|
|
229
|
-
|
|
230
|
-
# Parse property comment
|
|
231
|
-
prop_doc = parse_property_comment(comment)
|
|
232
|
-
|
|
233
|
-
# Extract options if present (for hash, singleton_array, etc.)
|
|
234
|
-
options = extract_property_options(args)
|
|
235
|
-
|
|
236
|
-
properties[property_name] = {
|
|
237
|
-
'type' => method_name.to_s,
|
|
238
|
-
'description' => prop_doc[:description],
|
|
239
|
-
'required' => options[:required] || false,
|
|
240
|
-
'options' => options[:options],
|
|
241
|
-
'examples' => prop_doc[:examples],
|
|
242
|
-
'notes' => prop_doc[:notes],
|
|
243
|
-
'deprecated' => prop_doc[:deprecated]
|
|
244
|
-
}.compact
|
|
245
|
-
|
|
246
|
-
# Remove empty arrays
|
|
247
|
-
properties[property_name].delete('examples') if properties[property_name]['examples']&.empty?
|
|
248
|
-
properties[property_name].delete('notes') if properties[property_name]['notes']&.empty?
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
properties
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
# Traverse class body to find method calls (property definitions)
|
|
256
|
-
def traverse_class_body(node, source, &block)
|
|
257
|
-
return unless node.is_a?(Parser::AST::Node)
|
|
258
|
-
|
|
259
|
-
if node.type == :send
|
|
260
|
-
receiver = node.children[0]
|
|
261
|
-
method_name = node.children[1]
|
|
262
|
-
args = node.children[2..-1].map { |arg| extract_symbol_or_string(arg) }
|
|
263
|
-
comment = extract_comment(node, source)
|
|
264
|
-
|
|
265
|
-
# Only process calls without a receiver (DSL methods)
|
|
266
|
-
yield method_name, args, comment if receiver.nil?
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
node.children.each do |child|
|
|
270
|
-
traverse_class_body(child, source, &block)
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
# Extract symbol or string value from AST node
|
|
275
|
-
def extract_symbol_or_string(node)
|
|
276
|
-
return nil unless node.is_a?(Parser::AST::Node)
|
|
277
|
-
|
|
278
|
-
case node.type
|
|
279
|
-
when :sym
|
|
280
|
-
node.children[0].to_s
|
|
281
|
-
when :str
|
|
282
|
-
node.children[0]
|
|
283
|
-
when :hash
|
|
284
|
-
extract_hash(node)
|
|
285
|
-
else
|
|
286
|
-
nil
|
|
287
|
-
end
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
# Extract hash from AST node
|
|
291
|
-
def extract_hash(node)
|
|
292
|
-
return nil unless node.type == :hash
|
|
293
|
-
|
|
294
|
-
result = {}
|
|
295
|
-
node.children.each do |pair|
|
|
296
|
-
next unless pair.type == :pair
|
|
297
|
-
key = extract_symbol_or_string(pair.children[0])
|
|
298
|
-
value = extract_symbol_or_string(pair.children[1]) || extract_array(pair.children[1])
|
|
299
|
-
result[key] = value if key
|
|
300
|
-
end
|
|
301
|
-
result
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
# Extract array from AST node
|
|
305
|
-
def extract_array(node)
|
|
306
|
-
return nil unless node.is_a?(Parser::AST::Node) && node.type == :array
|
|
307
|
-
|
|
308
|
-
node.children.map { |child| extract_symbol_or_string(child) }.compact
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Extract property options from arguments
|
|
312
|
-
def extract_property_options(args)
|
|
313
|
-
options = { required: false }
|
|
314
|
-
|
|
315
|
-
args.each do |arg|
|
|
316
|
-
if arg.is_a?(Hash)
|
|
317
|
-
if arg['required']
|
|
318
|
-
options[:required] = true
|
|
319
|
-
end
|
|
320
|
-
if arg['optional']
|
|
321
|
-
options[:options] = { 'optional' => arg['optional'] }
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
options
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
# Parse property comment
|
|
330
|
-
def parse_property_comment(comment)
|
|
331
|
-
return {} if comment.empty?
|
|
332
|
-
|
|
333
|
-
result = {
|
|
334
|
-
description: '',
|
|
335
|
-
examples: [],
|
|
336
|
-
notes: [],
|
|
337
|
-
deprecated: nil
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
description_lines = []
|
|
341
|
-
|
|
342
|
-
comment.lines.each do |line|
|
|
343
|
-
line = line.chomp
|
|
344
|
-
|
|
345
|
-
if line =~ /^@example\s+(.*)/
|
|
346
|
-
result[:examples] << $1.strip
|
|
347
|
-
elsif line =~ /^@note\s+(.*)/
|
|
348
|
-
result[:notes] << $1.strip
|
|
349
|
-
elsif line =~ /^@deprecated\s*(.*)/
|
|
350
|
-
result[:deprecated] = $1.strip
|
|
351
|
-
result[:deprecated] = true if result[:deprecated].empty?
|
|
352
|
-
else
|
|
353
|
-
description_lines << line unless line.strip.empty? && description_lines.empty?
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
result[:description] = description_lines.join("\n").strip
|
|
358
|
-
|
|
359
|
-
result
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
# Convert CamelCase to snake_case
|
|
363
|
-
def underscore(string)
|
|
364
|
-
string.gsub(/::/, '_')
|
|
365
|
-
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
366
|
-
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
367
|
-
.tr('-', '_')
|
|
368
|
-
.downcase
|
|
369
|
-
end
|
|
370
|
-
|
|
371
|
-
# Remove common leading whitespace from multi-line strings
|
|
372
|
-
def unindent(text)
|
|
373
|
-
return text if text.empty?
|
|
374
|
-
|
|
375
|
-
lines = text.lines
|
|
376
|
-
# Find minimum indentation (ignoring empty lines)
|
|
377
|
-
min_indent = lines
|
|
378
|
-
.reject { |line| line.strip.empty? }
|
|
379
|
-
.map { |line| line.match(/^(\s*)/)[1].length }
|
|
380
|
-
.min || 0
|
|
381
|
-
|
|
382
|
-
# Remove that amount of indentation from each line
|
|
383
|
-
lines.map { |line| line.strip.empty? ? line : line[min_indent..-1] }.join
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
end
|