glib-web 4.41.0 → 4.42.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.
- checksums.yaml +4 -4
- data/app/helpers/glib/json_ui/list_builders.rb +1 -1
- data/app/helpers/glib/json_ui/view_builder/fields.rb +1 -0
- data/app/helpers/glib/json_ui/view_builder.rb +8 -1
- data/app/views/json_ui/garage/lists/old_edit_mode.json.jbuilder +84 -0
- data/app/views/json_ui/garage/views/icons.json.jbuilder +74 -46
- data/lib/glib/doc_generator.rb +386 -0
- data/lib/tasks/db.rake +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fd0f52f48d55fef27f49ae004e3f2c59e730963cecaddd6af5cb5fe962b6323
|
|
4
|
+
data.tar.gz: aebd337997b0b656fac64f72a0c53322bd58815bdf2052aef4b15243d1e06e96
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dfa32067ee2a2be6dd8095bf8fce2b5da301cff44fdc93f1e80eba7fe9562ae12bae8f7edab9902c16e6ace86af262d028052fb30b644f2bb54957d61302571
|
|
7
|
+
data.tar.gz: e5d068af18a7f8908ae82aac1d5f74225aacdbd58eac2db8b0b78b07f9a5cb4e037d49f32aa11fb2278bc5341ce8c374a89fac4650f5b97f4227eb618606c136
|
|
@@ -274,10 +274,17 @@ module Glib
|
|
|
274
274
|
@size = value
|
|
275
275
|
end
|
|
276
276
|
|
|
277
|
+
def family(value)
|
|
278
|
+
@family = value.to_s
|
|
279
|
+
end
|
|
280
|
+
|
|
277
281
|
# Override
|
|
278
282
|
def created
|
|
279
283
|
if @name
|
|
280
|
-
|
|
284
|
+
icon_type = @family == 'fontawesome' ? 'fa' :
|
|
285
|
+
@family == 'custom' ? 'custom' : 'material'
|
|
286
|
+
|
|
287
|
+
json.set!(icon_type) do
|
|
281
288
|
json.name @name
|
|
282
289
|
json.size @size if @size
|
|
283
290
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
page = json_ui_page json
|
|
2
|
+
|
|
3
|
+
json.title 'Lists'
|
|
4
|
+
|
|
5
|
+
render "#{@path_prefix}/nav_menu", json: json, page: page
|
|
6
|
+
|
|
7
|
+
tab_index = params[:tab].to_i
|
|
8
|
+
|
|
9
|
+
page.header(
|
|
10
|
+
childViews: ->(header) do
|
|
11
|
+
# Allow navigating to another "edit mode" page to test reuse issues.
|
|
12
|
+
header.tabBar(
|
|
13
|
+
buttons: ->(menu) do
|
|
14
|
+
['FIRST', 'SECOND'].each_with_index do |text, index|
|
|
15
|
+
menu.button(
|
|
16
|
+
text: text,
|
|
17
|
+
disabled: tab_index == index,
|
|
18
|
+
onClick: ->(action) do
|
|
19
|
+
action.windows_reload url: json_ui_garage_url(path: 'lists/edit_mode', tab: index)
|
|
20
|
+
end
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
page.form(
|
|
29
|
+
width: 'matchParent',
|
|
30
|
+
url: json_ui_garage_url(path: 'forms/generic_post'),
|
|
31
|
+
method: 'post',
|
|
32
|
+
padding: glib_json_padding_body,
|
|
33
|
+
childViews: ->(form) do
|
|
34
|
+
form.panels_list(
|
|
35
|
+
fieldPrefix: 'user[items]',
|
|
36
|
+
fieldTitleName: 'name',
|
|
37
|
+
width: 'matchParent',
|
|
38
|
+
sections: [
|
|
39
|
+
->(section) do
|
|
40
|
+
section.header(
|
|
41
|
+
padding: glib_json_padding_list,
|
|
42
|
+
childViews: ->(header) do
|
|
43
|
+
header.panels_horizontal(
|
|
44
|
+
childViews: ->(horizontal) do
|
|
45
|
+
horizontal.fields_check name: 'user[check_all]', label: 'All', checkValue: true
|
|
46
|
+
horizontal.spacer width: 20
|
|
47
|
+
# header.fields_text width: 'matchParent', styleClass: 'outlined', name: 'user[new_name]', label: 'Item name'
|
|
48
|
+
statuses = [:pending, :active]
|
|
49
|
+
horizontal.fields_select(
|
|
50
|
+
styleClass: 'outlined',
|
|
51
|
+
name: 'user[status]',
|
|
52
|
+
width: 'matchParent',
|
|
53
|
+
label: 'Status',
|
|
54
|
+
options: statuses.map { |status| { value: status, text: status.to_s.humanize } }
|
|
55
|
+
)
|
|
56
|
+
horizontal.spacer width: 20
|
|
57
|
+
horizontal.fields_submit text: 'Update'
|
|
58
|
+
end
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
section.rows(
|
|
63
|
+
builder: ->(row) do
|
|
64
|
+
batch_count = 20
|
|
65
|
+
batch_count.times do |index|
|
|
66
|
+
id = (batch_count * tab_index) + index
|
|
67
|
+
# row.editable title: "Item #{id}", recordId: "PK_#{id}"
|
|
68
|
+
row.thumbnail title: "Item #{id}", recordId: "PK_#{id}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
],
|
|
74
|
+
fieldCheckValueIf: {
|
|
75
|
+
"==": [
|
|
76
|
+
{
|
|
77
|
+
"var": 'user[check_all]'
|
|
78
|
+
},
|
|
79
|
+
true
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
)
|
|
@@ -3,52 +3,80 @@ json.title 'Views'
|
|
|
3
3
|
page = json_ui_page json
|
|
4
4
|
render "#{@path_prefix}/nav_menu", json: json, page: page
|
|
5
5
|
|
|
6
|
-
page.form
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
page.form(
|
|
7
|
+
padding: glib_json_padding_body,
|
|
8
|
+
childViews: ->(scroll) do
|
|
9
|
+
scroll.h2 text: 'Icon'
|
|
10
|
+
# scroll.icon spec: { name: 'home' }
|
|
11
|
+
scroll.icon name: 'home'
|
|
12
|
+
scroll.spacer height: 20
|
|
13
|
+
scroll.h2 text: 'Icon with color'
|
|
14
|
+
scroll.fields_select(
|
|
15
|
+
name: 'input_icon',
|
|
16
|
+
value: 'verified',
|
|
17
|
+
options: ['pending', 'verified', 'cancel'].map { |icon_name| { text: icon_name, value: icon_name } }
|
|
18
|
+
)
|
|
19
|
+
scroll.button(
|
|
20
|
+
text: 'update',
|
|
21
|
+
onClick: ->(action) do
|
|
22
|
+
action.logics_set(
|
|
23
|
+
targetId: 'icon1',
|
|
24
|
+
conditionalData: {
|
|
25
|
+
'material.name': { "var": 'input_icon' },
|
|
26
|
+
'tooltip.text': { "var": 'input_icon' },
|
|
27
|
+
'tooltip.placement': 'bottom'
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
)
|
|
32
|
+
scroll.icon id: 'icon1', name: 'home', color: '#9370DB'
|
|
33
|
+
scroll.icon(
|
|
34
|
+
name: 'home',
|
|
35
|
+
color: '#9370DB80',
|
|
36
|
+
onClick: ->(action) do
|
|
37
|
+
action.snackbars_alert message: 'Icon clicked!'
|
|
38
|
+
end
|
|
39
|
+
)
|
|
40
|
+
scroll.spacer height: 20
|
|
41
|
+
scroll.h2 text: 'Icon with badge'
|
|
42
|
+
scroll.icon name: 'home', badge: { text: '22', backgroundColor: '#00ff00' }
|
|
43
|
+
scroll.spacer height: 20
|
|
44
|
+
scroll.h2 text: 'Icon with styleClasses'
|
|
45
|
+
scroll.icon id: 'icon2', name: 'home', styleClasses: ['small']
|
|
46
|
+
scroll.button(
|
|
47
|
+
text: 'Hide icon',
|
|
48
|
+
onClick: ->(action) do
|
|
49
|
+
action.logics_set(
|
|
50
|
+
targetId: 'icon2',
|
|
51
|
+
data: {
|
|
52
|
+
displayed: false
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
)
|
|
57
|
+
scroll.spacer height: 20
|
|
58
|
+
scroll.h2 text: 'Icon with onLoad'
|
|
59
|
+
scroll.icon(
|
|
60
|
+
id: 'icon3',
|
|
61
|
+
name: 'home',
|
|
62
|
+
onLoad: ->(action) do
|
|
63
|
+
action.logics_set(
|
|
64
|
+
targetId: 'icon3',
|
|
65
|
+
data: {
|
|
66
|
+
'material.name': 'info',
|
|
67
|
+
'tooltip.text': 'TEST123',
|
|
23
68
|
'tooltip.placement': 'bottom'
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
action.snackbars_alert message: 'Icon clicked!'
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
scroll.spacer height: 20
|
|
32
|
-
scroll.h2 text: 'Icon with badge'
|
|
33
|
-
scroll.icon name: 'home', badge: { text: '22', backgroundColor: '#00ff00' }
|
|
34
|
-
|
|
35
|
-
scroll.spacer height: 20
|
|
36
|
-
scroll.h2 text: 'Icon with styleClasses'
|
|
37
|
-
scroll.icon id: 'icon2', name: 'home', styleClasses: ['small']
|
|
38
|
-
|
|
39
|
-
scroll.button text: 'Hide icon', onClick: ->(action) do
|
|
40
|
-
action.logics_set targetId: 'icon2', data: {
|
|
41
|
-
displayed: false
|
|
42
|
-
}
|
|
43
|
-
end
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
)
|
|
44
73
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
74
|
+
# scroll.spacer height: 20
|
|
75
|
+
# scroll.h2 text: 'Icon using Font Awesome'
|
|
76
|
+
# scroll.icon(
|
|
77
|
+
# id: 'icon4',
|
|
78
|
+
# name: 'fa-solid fa-home',
|
|
79
|
+
# family: 'fontawesome'
|
|
80
|
+
# )
|
|
53
81
|
end
|
|
54
|
-
|
|
82
|
+
)
|
|
@@ -0,0 +1,386 @@
|
|
|
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
|
data/lib/tasks/db.rake
CHANGED
metadata
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: glib-web
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.
|
|
4
|
+
version: 4.42.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ''
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2019-10-04 00:00:00.000000000 Z
|
|
@@ -136,7 +136,7 @@ dependencies:
|
|
|
136
136
|
- - ">="
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
138
|
version: '0'
|
|
139
|
-
description:
|
|
139
|
+
description:
|
|
140
140
|
email: ''
|
|
141
141
|
executables: []
|
|
142
142
|
extensions: []
|
|
@@ -317,6 +317,7 @@ files:
|
|
|
317
317
|
- app/views/json_ui/garage/lists/edit_mode.json.jbuilder
|
|
318
318
|
- app/views/json_ui/garage/lists/fab.json.jbuilder
|
|
319
319
|
- app/views/json_ui/garage/lists/index.json.jbuilder
|
|
320
|
+
- app/views/json_ui/garage/lists/old_edit_mode.json.jbuilder
|
|
320
321
|
- app/views/json_ui/garage/lists/reordering.json.jbuilder
|
|
321
322
|
- app/views/json_ui/garage/lists/templating.json.jbuilder
|
|
322
323
|
- app/views/json_ui/garage/multistep_form/step1.json.jbuilder
|
|
@@ -427,6 +428,7 @@ files:
|
|
|
427
428
|
- lib/glib-web.rb
|
|
428
429
|
- lib/glib/all_helpers.rb
|
|
429
430
|
- lib/glib/crypt/utils.rb
|
|
431
|
+
- lib/glib/doc_generator.rb
|
|
430
432
|
- lib/glib/dynamic_text.rb
|
|
431
433
|
- lib/glib/dynamic_text/config.rb
|
|
432
434
|
- lib/glib/engine.rb
|
|
@@ -462,10 +464,10 @@ files:
|
|
|
462
464
|
- lib/glib/version.rb
|
|
463
465
|
- lib/tasks/db.rake
|
|
464
466
|
- lib/tasks/docs.rake
|
|
465
|
-
homepage:
|
|
467
|
+
homepage:
|
|
466
468
|
licenses: []
|
|
467
469
|
metadata: {}
|
|
468
|
-
post_install_message:
|
|
470
|
+
post_install_message:
|
|
469
471
|
rdoc_options: []
|
|
470
472
|
require_paths:
|
|
471
473
|
- lib
|
|
@@ -481,7 +483,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
481
483
|
version: '0'
|
|
482
484
|
requirements: []
|
|
483
485
|
rubygems_version: 3.4.6
|
|
484
|
-
signing_key:
|
|
486
|
+
signing_key:
|
|
485
487
|
specification_version: 4
|
|
486
488
|
summary: ''
|
|
487
489
|
test_files: []
|