dudity 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.
- checksums.yaml +7 -0
- data/lib/download_service.rb +10 -0
- data/lib/dudes.rb +82 -0
- data/lib/dudity.rb +155 -0
- data/lib/git_diff_service.rb +119 -0
- data/lib/process_code_service.rb +87 -0
- data/lib/scan_app.rb +42 -0
- metadata +49 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 2f4f733d0f1d5e557bf7a80436baa2a66a3fe8d689a313f0eed01b728ac3d65d
|
4
|
+
data.tar.gz: 945da3f26fcaa3656824de52f471731fe091d5fe7c991ff81d45af46cfb0b8db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 272d70947283eb90ebcd0248d7a2813b5fcb26b88e750d735c788cb6176ca41acf9cdd209dfc732dc3e3d723b2f2d7dd9f3eba5bbe54dea90033917f9b2b83cb
|
7
|
+
data.tar.gz: 33a6432c3575142ecea4d6de618748f3c94762b838e105e0037ed6a775138f6d0ac2707c35f2fc04ce795ec1eb9a72306ca3b23c5714de5da1750f3020d074c6
|
data/lib/dudes.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# this module was created by Victor Shepelev (zverok) https://github.com/zverok
|
2
|
+
require 'fast'
|
3
|
+
|
4
|
+
module Dudes
|
5
|
+
class Calculator
|
6
|
+
using(Module.new do
|
7
|
+
refine Astrolabe::Node do
|
8
|
+
def fast(path)
|
9
|
+
Fast.search(path, self)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Lot of chunks of code parsed as (:foo, ...) if it is a single statement,
|
13
|
+
# or (:begin, (:foo, ...), (:bar, ...)) if it is several independent statements.
|
14
|
+
# Robustly make it an array of nodes
|
15
|
+
def arrayify
|
16
|
+
type == :begin ? children : [self]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end)
|
20
|
+
|
21
|
+
def initialize(code)
|
22
|
+
@ast = Fast.ast(code)
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
return [] unless @ast
|
27
|
+
@ast.arrayify.map(&method(:calc_class)).compact
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def calc_class(node)
|
33
|
+
return unless node.type == :class
|
34
|
+
|
35
|
+
name, parent, body = *node
|
36
|
+
{
|
37
|
+
name: name.children.compact.join(':'),
|
38
|
+
methods: body&.arrayify&.map(&method(:calc_method))&.compact || [],
|
39
|
+
references: extract_references(body)
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def extract_references(node)
|
44
|
+
return [] unless node
|
45
|
+
|
46
|
+
node.fast('(const {nil _} )').map { |n| n.children.compact.join('::') }.sort.uniq
|
47
|
+
end
|
48
|
+
|
49
|
+
def calc_method(node)
|
50
|
+
return unless node.type == :def
|
51
|
+
|
52
|
+
name, args, body = *node
|
53
|
+
|
54
|
+
return empty_body(name, args) if body.nil?
|
55
|
+
|
56
|
+
{
|
57
|
+
name: name,
|
58
|
+
args: args.children.count,
|
59
|
+
length: count_statements(body.arrayify),
|
60
|
+
conditions: count_conditions(body),
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def empty_body(name, args)
|
65
|
+
{
|
66
|
+
name: name,
|
67
|
+
args: args.children.count,
|
68
|
+
length: 0,
|
69
|
+
conditions: 0,
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
# FIXME: This is kinda naive... But maybe appropriate enough
|
74
|
+
def count_statements(nodes)
|
75
|
+
nodes.sum { |n| n.each_node.count }
|
76
|
+
end
|
77
|
+
|
78
|
+
def count_conditions(node)
|
79
|
+
node.fast('({if case} _)').count
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/lib/dudity.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'dudegl'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
Dir[File.dirname(__FILE__) + '/**/*.rb'].each {|file| require_relative file }
|
5
|
+
|
6
|
+
class Dudity
|
7
|
+
class << self
|
8
|
+
def visualise(path, opt = {})
|
9
|
+
@path = path
|
10
|
+
opt.key?(:except) ? except = opt[:except] : except = nil
|
11
|
+
opt.key?(:only) ? only = opt[:only] : only = nil
|
12
|
+
opt.key?(:ignore_classes) ? ignore_classes = opt[:ignore_classes] : ignore_classes = nil
|
13
|
+
opt.key?(:only_classes) ? only_classes = opt[:only_classes] : only_classes = nil
|
14
|
+
opt.key?(:filename_suffix) ? filename_suffix = opt[:filename_suffix] : filename_suffix = ''
|
15
|
+
|
16
|
+
project_files = ScanApp.call(@path, except, only)
|
17
|
+
app_name = @path.split('/').last
|
18
|
+
|
19
|
+
@params_list = []
|
20
|
+
project_files.each { |project_file| process_item(project_file) }
|
21
|
+
|
22
|
+
@params_list = @params_list.flatten.compact
|
23
|
+
exclude_classes(ignore_classes) if ignore_classes
|
24
|
+
include_classes(only_classes) if only_classes
|
25
|
+
|
26
|
+
dudes = DudeGl.new @params_list, dudes_per_row_max: 4
|
27
|
+
dudes.render
|
28
|
+
dudes.save "#{app_name}#{filename_suffix}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def visualise_diff(path_to_diff, opt = {})
|
32
|
+
@params1 = []
|
33
|
+
@params2 = []
|
34
|
+
|
35
|
+
# path to the dir where diff file is stored
|
36
|
+
@path = path_to_diff.split('/').take(path_to_diff.split('/').size - 1).join('/')
|
37
|
+
|
38
|
+
opt.key?(:as) ? file_type = opt[:as] : file_type = :svg
|
39
|
+
opt.key?(:pull_branch) ? @pull_branch = opt[:pull_branch] : return
|
40
|
+
|
41
|
+
diff = open(path_to_diff).readlines
|
42
|
+
@diff_data = GitDiffService.call(diff)
|
43
|
+
|
44
|
+
return generate_svg if file_type == :svg
|
45
|
+
return generate_html_report if file_type == :html
|
46
|
+
end
|
47
|
+
|
48
|
+
def visualise_pr(public_pr_link, opt = {})
|
49
|
+
@params1 = []
|
50
|
+
@params2 = []
|
51
|
+
|
52
|
+
@path = public_pr_link
|
53
|
+
diff_url = "#{public_pr_link}.diff"
|
54
|
+
|
55
|
+
opt.key?(:as) ? file_type = opt[:as] : file_type = :svg
|
56
|
+
opt.key?(:pull_branch) ? @pull_branch = opt[:pull_branch] : return
|
57
|
+
|
58
|
+
diff = DownloadService.call(diff_url, :read_by_line)
|
59
|
+
@diff_data = GitDiffService.call(diff)
|
60
|
+
|
61
|
+
return generate_svg if file_type == :svg
|
62
|
+
return generate_html_report if file_type == :html
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
# generate name based on pull request data. Example: DudesHub_pull_5
|
68
|
+
def fname
|
69
|
+
@path.split('/')[-3, 3].join('_')
|
70
|
+
end
|
71
|
+
|
72
|
+
# generate html report title based on repo data, make each word capitalized
|
73
|
+
def report_title
|
74
|
+
@path.split('/')[-3, 3].map(&:capitalize).join(' ')
|
75
|
+
end
|
76
|
+
|
77
|
+
def generate_svg
|
78
|
+
analyze_code(@diff_data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def generate_html_report
|
82
|
+
separate_code
|
83
|
+
analyze_by_category
|
84
|
+
|
85
|
+
html = open('templates/dudes_report.html').read
|
86
|
+
html = html.sub('[dudes_report_title]', report_title)
|
87
|
+
@report_file_path = "#{fname}.html"
|
88
|
+
File.open(@report_file_path, 'w') { |file| file.write(html) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def separate_code
|
92
|
+
@diff_data_controllers = []
|
93
|
+
@diff_data_models = []
|
94
|
+
@diff_data_others = []
|
95
|
+
|
96
|
+
@diff_data.each do |item|
|
97
|
+
if item[:old_name]&.start_with?("app/controllers") || item[:new_name]&.start_with?("app/controllers")
|
98
|
+
@diff_data_controllers << item
|
99
|
+
elsif item[:old_name]&.start_with?("app/models") || item[:new_name]&.start_with?("app/models")
|
100
|
+
@diff_data_models << item
|
101
|
+
else
|
102
|
+
@diff_data_others << item
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def analyze_by_category
|
108
|
+
analyze_code(@diff_data_controllers, :controllers) if !@diff_data_controllers.empty?
|
109
|
+
analyze_code(@diff_data_models, :models) if !@diff_data_models.empty?
|
110
|
+
analyze_code(@diff_data_others, :others) if !@diff_data_others.empty?
|
111
|
+
end
|
112
|
+
|
113
|
+
def analyze_code(diff_data, label = nil)
|
114
|
+
@params1 = []
|
115
|
+
@params2 = []
|
116
|
+
local = !@path.start_with?('http')
|
117
|
+
local ? suffix = '_local' : suffix = ''
|
118
|
+
|
119
|
+
diff_data.map { |item| process_item_for_diff(item, local) }
|
120
|
+
|
121
|
+
renamed = diff_data.select { |item| item[:status] == :renamed_class }
|
122
|
+
|
123
|
+
return false if params_empty?
|
124
|
+
|
125
|
+
dudes = DudeGl.new [@params1.flatten.compact, @params2.flatten.compact],
|
126
|
+
dudes_per_row_max: 4, renamed: renamed, diff: true
|
127
|
+
dudes.render
|
128
|
+
|
129
|
+
label ? dudes.save("#{label}#{suffix}") : dudes.save("#{fname}#{suffix}")
|
130
|
+
end
|
131
|
+
|
132
|
+
def process_item(project_file)
|
133
|
+
code = open(project_file).read
|
134
|
+
@params_list << Dudes::Calculator.new(code).call
|
135
|
+
end
|
136
|
+
|
137
|
+
def process_item_for_diff(item, local = false)
|
138
|
+
processed_code = ProcessCodeService.new(@path, @pull_branch, item, local = local).call
|
139
|
+
@params1 << processed_code.first
|
140
|
+
@params2 << processed_code.last
|
141
|
+
end
|
142
|
+
|
143
|
+
def params_empty?
|
144
|
+
@params1.empty? || @params2.empty?
|
145
|
+
end
|
146
|
+
|
147
|
+
def exclude_classes(classes)
|
148
|
+
@params_list = @params_list.reject { |param| classes.any? { |item| param[:name] == item } }
|
149
|
+
end
|
150
|
+
|
151
|
+
def include_classes(classes)
|
152
|
+
@params_list = @params_list.select { |param| classes.any? { |item| param[:name] == item } }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
class GitDiffService
|
2
|
+
class << self
|
3
|
+
PATTERN_MATCH = /^diff --git\s/
|
4
|
+
# restrict scope: for Rails app exclude autogenerated code and omit any files except .rb
|
5
|
+
EXCLUDE_ROOT_FOLDERS = ['db/', 'spec/', 'config/']
|
6
|
+
ALLOWED_EXTENSION = '.rb'
|
7
|
+
|
8
|
+
def call(diff_lines, exclude_folders = [])
|
9
|
+
@diff_lines = diff_lines
|
10
|
+
# TO-DO: support @only_folders
|
11
|
+
@exclude_folders = exclude_folders + EXCLUDE_ROOT_FOLDERS
|
12
|
+
|
13
|
+
patch_pos_start = lines_with_file_paths.map { |line| patch_start_pos(line) }
|
14
|
+
# array with start and end positions for each patch
|
15
|
+
@patches_pos = patch_pos_start.zip(patch_end_pos(patch_pos_start))
|
16
|
+
slice_diff
|
17
|
+
@patches = @patches.select { |patch| allowed_patch?(patch) }
|
18
|
+
@diff_data = @patches.map { |patch| fname_diff(patch) }
|
19
|
+
@diff_data += @patches.map { |patch| class_diff(patch) }
|
20
|
+
@diff_data.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# split diff lines into separate patches for each file
|
26
|
+
def slice_diff
|
27
|
+
@patches = @patches_pos.map { |pos| @diff_lines[pos.first..pos.last] }
|
28
|
+
end
|
29
|
+
|
30
|
+
def allowed_patch?(patch)
|
31
|
+
fname_string = patch.first
|
32
|
+
extension_allowed = extract_file_paths(fname_string).last.end_with?(ALLOWED_EXTENSION)
|
33
|
+
folder_allowed = !extract_file_paths(fname_string).last.start_with?(*@exclude_folders)
|
34
|
+
|
35
|
+
extension_allowed && folder_allowed
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_file?(patch)
|
39
|
+
patch.select { |line| line.start_with?('new file mode ') }.any?
|
40
|
+
end
|
41
|
+
|
42
|
+
def deleted_file?(patch)
|
43
|
+
patch.select { |line| line.start_with?('deleted file mode ') }.any?
|
44
|
+
end
|
45
|
+
|
46
|
+
def renamed_file?(patch)
|
47
|
+
patch.select { |line| line.start_with?('rename from ') }.any?
|
48
|
+
end
|
49
|
+
|
50
|
+
def class_renamed?(patch)
|
51
|
+
patch.select { |line| line.start_with?('-class') }.any? &&
|
52
|
+
patch.select { |line| line.start_with?('+class') }.any?
|
53
|
+
end
|
54
|
+
|
55
|
+
def old_class_name(patch)
|
56
|
+
res = patch.select { |line| line.start_with?('-class') }
|
57
|
+
return if res.empty?
|
58
|
+
line = res.first
|
59
|
+
line.split(' ').last.strip
|
60
|
+
end
|
61
|
+
|
62
|
+
def new_class_name(patch)
|
63
|
+
res = patch.select { |line| line.start_with?('+class') }
|
64
|
+
return if res.empty?
|
65
|
+
line = res.first
|
66
|
+
line.split(' ').last.strip
|
67
|
+
end
|
68
|
+
|
69
|
+
def fname_diff(patch)
|
70
|
+
# first line in patch contains info about file path
|
71
|
+
file_path_line = patch.first
|
72
|
+
return { old_name: extract_file_paths(file_path_line).first,
|
73
|
+
new_name: extract_file_paths(file_path_line).last, status: :renamed } if renamed_file?(patch)
|
74
|
+
return { old_name: nil, new_name: extract_file_paths(file_path_line).last,
|
75
|
+
status: :new } if new_file?(patch)
|
76
|
+
return { old_name: extract_file_paths(file_path_line).first,
|
77
|
+
new_name: nil, status: :deleted } if deleted_file?(patch)
|
78
|
+
# changed file without renaming
|
79
|
+
return { old_name: extract_file_paths(file_path_line).first,
|
80
|
+
new_name: extract_file_paths(file_path_line).last, status: :changed }
|
81
|
+
end
|
82
|
+
|
83
|
+
def class_diff(patch)
|
84
|
+
return { old_name: old_class_name(patch),
|
85
|
+
new_name: new_class_name(patch), status: :renamed_class } if class_renamed?(patch)
|
86
|
+
end
|
87
|
+
|
88
|
+
# end line index for current patch
|
89
|
+
def patch_end_pos(patch_pos_start)
|
90
|
+
end_line_pos = patch_pos_start.size - 1
|
91
|
+
pos_end = patch_pos_start.map.with_index do |pos, i|
|
92
|
+
i == patch_pos_start.size - 1 ? @diff_lines.size - 1 : patch_pos_start[i + 1] - 1
|
93
|
+
end
|
94
|
+
|
95
|
+
pos_end
|
96
|
+
end
|
97
|
+
|
98
|
+
def lines_with_file_paths
|
99
|
+
@diff_lines.select { |line| line.scan(PATTERN_MATCH).any? }
|
100
|
+
end
|
101
|
+
|
102
|
+
def patch_start_pos(line)
|
103
|
+
@diff_lines.index(line)
|
104
|
+
end
|
105
|
+
|
106
|
+
def extract_file_paths(line)
|
107
|
+
# diff --git a/project_v1/README.md b/project_v2/README2.md
|
108
|
+
line = line.delete_prefix('diff --git ')
|
109
|
+
path1, path2 = line.split(' ')
|
110
|
+
[path1, path2].map! { |path| file_path(path) }
|
111
|
+
end
|
112
|
+
|
113
|
+
def file_path(str)
|
114
|
+
# a/project_v1/README.md -> project_v1/README.md
|
115
|
+
prefix, *path_parts = str.split('/')
|
116
|
+
path_parts.join('/')
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# use source code of gems to simplify their code editing (if improvements are needed or bugs are found)
|
2
|
+
# in the final version of app gems will be used
|
3
|
+
require '/Users/dmkp/Documents/code/ruby/dudes/zverok_dudes_fork2/dudes/lib/dudes.rb'
|
4
|
+
|
5
|
+
class ProcessCodeService
|
6
|
+
# path is link to pr or treated as local path if local = true
|
7
|
+
def initialize(path, pull_branch, file_data, local = false)
|
8
|
+
@path = path
|
9
|
+
@pull_branch = pull_branch
|
10
|
+
@file_data = file_data
|
11
|
+
@local = local
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
process_code_file
|
16
|
+
|
17
|
+
[@params1, @params2]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def process_code_file
|
23
|
+
download_master_code
|
24
|
+
download_pull_request_code
|
25
|
+
end
|
26
|
+
|
27
|
+
def download_master_code
|
28
|
+
return unless [:deleted, :changed, :renamed].include?(@file_data[:status])
|
29
|
+
code_master = get_code(code_master_branch_path)
|
30
|
+
# code_master = DownloadService.call(code_master_branch_path)
|
31
|
+
code_hash = Dudes::Calculator.new(code_master).call
|
32
|
+
@params1 = code_hash.first unless code_hash.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def download_pull_request_code
|
36
|
+
return unless [:new, :changed, :renamed].include?(@file_data[:status])
|
37
|
+
code_pull_request = get_code(code_pull_request_branch_path)
|
38
|
+
# code_pull_request = DownloadService.call(code_pull_request_branch_path)
|
39
|
+
code_hash = Dudes::Calculator.new(code_pull_request).call
|
40
|
+
@params2 = code_hash.first unless code_hash.empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_code(path)
|
44
|
+
return open(path).read if @local
|
45
|
+
return DownloadService.call(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def code_master_branch_path
|
49
|
+
code_path = extract_code_path(:master)
|
50
|
+
return "#{@path}/#{repo_full_name}-master/#{code_path}" if @local
|
51
|
+
return "https://raw.githubusercontent.com/#{repo_full_name}/master/#{code_path}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def code_pull_request_branch_path
|
55
|
+
code_path = extract_code_path(:pull)
|
56
|
+
return "#{@path}/#{repo_full_name}-#{@pull_branch}/#{code_path}" if @local
|
57
|
+
return "https://raw.githubusercontent.com/#{repo_full_name}/#{@pull_branch}/#{code_path}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def repo_full_name
|
61
|
+
return @path.split('/').last if @local
|
62
|
+
return @path.split('/')[-4, 2].join('/')
|
63
|
+
end
|
64
|
+
|
65
|
+
def extract_code_path(branch)
|
66
|
+
return @file_data[:old_name] if deleted?
|
67
|
+
return @file_data[:new_name] if new? || changed?
|
68
|
+
return renamed?(branch)
|
69
|
+
end
|
70
|
+
|
71
|
+
def deleted?
|
72
|
+
@file_data[:status] == :deleted
|
73
|
+
end
|
74
|
+
|
75
|
+
def new?
|
76
|
+
@file_data[:status] == :new
|
77
|
+
end
|
78
|
+
|
79
|
+
def changed?
|
80
|
+
@file_data[:status] == :changed
|
81
|
+
end
|
82
|
+
|
83
|
+
def renamed?(branch)
|
84
|
+
return @file_data[:old_name] if branch == :master
|
85
|
+
return @file_data[:new_name] if branch == :pull
|
86
|
+
end
|
87
|
+
end
|
data/lib/scan_app.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
class ScanApp
|
2
|
+
class << self
|
3
|
+
EXCLUDE_FOLDERS = ['db/', 'spec/', 'config/']
|
4
|
+
ALLOWED_EXTENSIONS = ['.rb']
|
5
|
+
|
6
|
+
def call(path, except, only)
|
7
|
+
@except = except
|
8
|
+
@only = only
|
9
|
+
# list of all files from app dir recursively
|
10
|
+
@project_files = Dir.glob("#{path}/**/*")
|
11
|
+
|
12
|
+
filter_by_extension
|
13
|
+
exclude_default
|
14
|
+
|
15
|
+
exclude_files if @except
|
16
|
+
include_only_files if @only
|
17
|
+
@project_files
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def exclude_default
|
23
|
+
@project_files = @project_files.reject { |project_file| match?(project_file, EXCLUDE_FOLDERS) }
|
24
|
+
end
|
25
|
+
|
26
|
+
def filter_by_extension
|
27
|
+
@project_files = @project_files.select { |project_file| project_file.end_with?(*ALLOWED_EXTENSIONS) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def exclude_files
|
31
|
+
@project_files = @project_files.reject { |project_file| match?(project_file, @except) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def include_only_files
|
35
|
+
@project_files = @project_files.select { |project_file| match?(project_file, @only) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def match?(string, matches)
|
39
|
+
matches.any? { |item| string.include?(item) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
metadata
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dudity
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dmitry Khramtsov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Try DudeGL code visualization in your Rails projects
|
14
|
+
email:
|
15
|
+
- dp@khramtsov.net
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/download_service.rb
|
21
|
+
- lib/dudes.rb
|
22
|
+
- lib/dudity.rb
|
23
|
+
- lib/git_diff_service.rb
|
24
|
+
- lib/process_code_service.rb
|
25
|
+
- lib/scan_app.rb
|
26
|
+
homepage: https://github.com/dmikhr/Dudity
|
27
|
+
licenses:
|
28
|
+
- MIT
|
29
|
+
metadata: {}
|
30
|
+
post_install_message:
|
31
|
+
rdoc_options: []
|
32
|
+
require_paths:
|
33
|
+
- lib
|
34
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubygems_version: 3.0.3
|
46
|
+
signing_key:
|
47
|
+
specification_version: 4
|
48
|
+
summary: Analyze Rails code with stick dudes
|
49
|
+
test_files: []
|