iosappaudit 1.0.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/.gitignore +8 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/default_configuration.yaml +11 -0
- data/exe/iosappaudit +4 -0
- data/i18n/en.yml +35 -0
- data/iosappaudit.gemspec +43 -0
- data/lib/iosappaudit.rb +37 -0
- data/lib/iosappaudit/Helper/file_seeker.rb +20 -0
- data/lib/iosappaudit/Helper/options_parser.rb +19 -0
- data/lib/iosappaudit/Presenter/CSVReportPresenter.rb +94 -0
- data/lib/iosappaudit/Review/complexity_report.rb +70 -0
- data/lib/iosappaudit/Review/complexity_report_parser.rb +50 -0
- data/lib/iosappaudit/Review/complexity_reviewer.rb +18 -0
- data/lib/iosappaudit/Review/project_report.rb +45 -0
- data/lib/iosappaudit/Review/project_reviewer.rb +61 -0
- data/lib/iosappaudit/version.rb +3 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '020928007442acd4e62981306e047324cd18bbae4639a3abfa989d2451fb68e2'
|
4
|
+
data.tar.gz: eae2e1627e5f9e20f74cb99ae865b75fb6919a660545a92313557781aafe2358
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 74575f011ea5d86e3d027901e84155d34fcd9cc1b74ffddc1d4ba0ca29bb01efa6cb6cb5e82b1afce0e1fd9e9225b90c7549837ed9c1ecd7569b016e23ddf45c
|
7
|
+
data.tar.gz: bf7062f0c587b10604320744d7b81b4ad04a5a56c2ac817b1af0999ed94f835755dc6c3e6bb2ee52ec3d4e1fd00ea4052e3f630c623859c6009ba3617764633b
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "iosappaudit"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
project_url: "" # required
|
2
|
+
sources_url: "" # required
|
3
|
+
xcodeproj:
|
4
|
+
name: "" # first xcodeproj found will be used if empty
|
5
|
+
main_target_name: "" # first target will be used if empty
|
6
|
+
complexity:
|
7
|
+
file_line_count_threshold: 500
|
8
|
+
output_format:
|
9
|
+
adds_row_padding: true
|
10
|
+
size: 0
|
11
|
+
csv_output: audit.csv
|
data/exe/iosappaudit
ADDED
data/i18n/en.yml
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
section:
|
2
|
+
project:
|
3
|
+
title: Project
|
4
|
+
row:
|
5
|
+
name: Name
|
6
|
+
version: Version
|
7
|
+
deployment_target: Deployment Target
|
8
|
+
localizations: Localizations
|
9
|
+
settings:
|
10
|
+
title: Settings
|
11
|
+
row:
|
12
|
+
schemes: Schemes
|
13
|
+
targets: Targets
|
14
|
+
configurations: Configurations
|
15
|
+
complexity:
|
16
|
+
title: Code
|
17
|
+
row:
|
18
|
+
file_count: Files
|
19
|
+
file_count_threshold: !!pl
|
20
|
+
1: Files with more than one line
|
21
|
+
n: Files with more than %1 lines
|
22
|
+
swift_file_count: Swift files
|
23
|
+
obj_c_file_count: Objective C files
|
24
|
+
code_line_count: Lines of code
|
25
|
+
ccn_line_count: Cyclomatic Complexity Number
|
26
|
+
resources:
|
27
|
+
title: Resources
|
28
|
+
row:
|
29
|
+
xib_count: Xibs
|
30
|
+
storyboards: Storyboards
|
31
|
+
tests:
|
32
|
+
title: Tests
|
33
|
+
row:
|
34
|
+
unit_tests: Unit Tests
|
35
|
+
ui_tests: UI Tests
|
data/iosappaudit.gemspec
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "iosappaudit/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = 'iosappaudit'
|
8
|
+
s.version = IOSAppAudit::VERSION
|
9
|
+
s.date = '2019-01-23'
|
10
|
+
s.summary = "Audit"
|
11
|
+
s.description = "Audit"
|
12
|
+
s.authors = ["Gaétan Zanella"]
|
13
|
+
s.email = 'gaetan.zanella@gmail.com'
|
14
|
+
# use `git ls-files -coz -x *.gem` for development
|
15
|
+
|
16
|
+
s.homepage = 'http://rubygems.org/gems/hola'
|
17
|
+
s.license = 'MIT'
|
18
|
+
s.add_dependency "xcodeproj", "~> 1.4"
|
19
|
+
s.add_dependency "r18n-core", "~> 3.2"
|
20
|
+
s.add_dependency "colorize", "~> 0.8"
|
21
|
+
s.add_development_dependency "byebug", "~> 10.0"
|
22
|
+
|
23
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
24
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
25
|
+
if s.respond_to?(:metadata)
|
26
|
+
s.metadata["allowed_push_host"] = "https://rubygems.org"
|
27
|
+
s.metadata["homepage_uri"] = s.homepage
|
28
|
+
s.metadata["source_code_uri"] = "https://github.com/faberNovel/iosappaudit"
|
29
|
+
s.metadata["changelog_uri"] = "https://github.com/faberNovel/iosappaudit/blob/master/CHANGELOG.md"
|
30
|
+
else
|
31
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
32
|
+
"public gem pushes."
|
33
|
+
end
|
34
|
+
|
35
|
+
# Specify which files should be added to the gem when it is released.
|
36
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
37
|
+
s.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
38
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
39
|
+
end
|
40
|
+
s.bindir = "exe"
|
41
|
+
s.executables = ["iosappaudit"]
|
42
|
+
s.require_paths = ["lib"]
|
43
|
+
end
|
data/lib/iosappaudit.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'xcodeproj'
|
3
|
+
require 'rexml/document'
|
4
|
+
require 'r18n-core'
|
5
|
+
require 'CSV'
|
6
|
+
require 'yaml'
|
7
|
+
require 'JSON'
|
8
|
+
require 'fileutils'
|
9
|
+
require 'colorize'
|
10
|
+
|
11
|
+
require_relative "iosappaudit/Review/complexity_report.rb"
|
12
|
+
require_relative "iosappaudit/Review/complexity_report_parser.rb"
|
13
|
+
require_relative "iosappaudit/Review/complexity_reviewer.rb"
|
14
|
+
require_relative "iosappaudit/Review/project_report.rb"
|
15
|
+
require_relative "iosappaudit/Review/project_reviewer.rb"
|
16
|
+
require_relative "iosappaudit/Helper/file_seeker.rb"
|
17
|
+
require_relative "iosappaudit/Helper/options_parser.rb"
|
18
|
+
require_relative "iosappaudit/Presenter/CSVReportPresenter.rb"
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
OptionParser.new do |parser|
|
22
|
+
parser.on("-o", "--option=URL") do |url|
|
23
|
+
options[:url] = url
|
24
|
+
end
|
25
|
+
end.parse!
|
26
|
+
|
27
|
+
properties_parser = Helper::OptionsParser.new
|
28
|
+
properties = properties_parser.parse options
|
29
|
+
|
30
|
+
complexity_reviewer = Review::ComplexityReviewer.new
|
31
|
+
complexity_report = complexity_reviewer.review_folder properties
|
32
|
+
|
33
|
+
project_reviewer = Review::ProjectReviewer.new
|
34
|
+
project_report = project_reviewer.review_folder properties
|
35
|
+
|
36
|
+
presenter = Presenter::CSVReportPresenter.new properties, project_report, complexity_report
|
37
|
+
presenter.generate_review
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Helper
|
2
|
+
class FileSeeker
|
3
|
+
|
4
|
+
def initialize()
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
def find_files(url, name, extension)
|
9
|
+
Dir.glob("#{url}/**/#{name}.#{extension}")
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_files_with_extension(url, extension)
|
13
|
+
Dir.glob("#{url}/**/*.#{extension}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_file_names_with_extension(url, extension)
|
17
|
+
find_files_with_extension(url, extension).map { |url| File.basename(url) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Helper
|
2
|
+
class OptionsParser
|
3
|
+
def parse(options)
|
4
|
+
default_properties = JSON.parse(YAML::load_file(__dir__ + "/../../../default_configuration.yaml").to_json, object_class: Hash)
|
5
|
+
properties = JSON.parse(YAML::load_file(options[:url]).to_json, object_class: Hash)
|
6
|
+
default_properties.merge! properties
|
7
|
+
symbolizeOptions default_properties
|
8
|
+
end
|
9
|
+
|
10
|
+
def symbolizeOptions(options)
|
11
|
+
options.inject({}) { |new_hash, key_value|
|
12
|
+
key, value = key_value
|
13
|
+
value = symbolizeOptions(value) if value.is_a?(Hash)
|
14
|
+
new_hash[key.to_sym] = value
|
15
|
+
new_hash
|
16
|
+
}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
include R18n::Helpers
|
2
|
+
|
3
|
+
module Presenter
|
4
|
+
class CSVReportPresenter
|
5
|
+
|
6
|
+
def initialize(options, project_report, complexity_report)
|
7
|
+
@project_report = project_report
|
8
|
+
@complexity_report = complexity_report
|
9
|
+
@options = options
|
10
|
+
root = File.expand_path '../../..', File.dirname(__FILE__)
|
11
|
+
R18n.default_places = "#{root}/i18n/"
|
12
|
+
R18n.set('en')
|
13
|
+
end
|
14
|
+
|
15
|
+
def generate_review
|
16
|
+
puts "Generating csv review..."
|
17
|
+
url = @options[:csv_output]
|
18
|
+
CSV.open(url, "wb") do |csv|
|
19
|
+
# Project
|
20
|
+
csv << section(t.section.project.title)
|
21
|
+
csv << value_row(t.section.project.row.name, @project_report.name)
|
22
|
+
csv << value_row(t.section.project.row.version, @project_report.version)
|
23
|
+
csv << value_row(t.section.project.row.deployment_target, @project_report.deployment_target.first)
|
24
|
+
csv << array_row(t.section.project.row.localizations, @project_report.localizations)
|
25
|
+
# Settings
|
26
|
+
csv << section(t.section.settings.title)
|
27
|
+
csv << array_row(t.section.settings.row.targets, @project_report.target_names)
|
28
|
+
csv << array_row(t.section.settings.row.configurations, @project_report.configuration_names)
|
29
|
+
# Complexity
|
30
|
+
csv << section(t.section.complexity.title)
|
31
|
+
csv << array_row(t.section.complexity.row.file_count, @project_report.main_target_files)
|
32
|
+
csv << array_row(t.section.complexity.row.swift_file_count, @project_report.main_target_swift_files)
|
33
|
+
csv << array_row(t.section.complexity.row.obj_c_file_count, @project_report.main_target_obj_c_files)
|
34
|
+
csv << value_row(t.section.complexity.row.code_line_count, @complexity_report.file_measure.sum_ncss)
|
35
|
+
file_line_count_threshold = @options[:complexity][:file_line_count_threshold]
|
36
|
+
metrics_ncss = @complexity_report.file_metrics_with_more_than_count_lines @options[:complexity][:file_line_count_threshold]
|
37
|
+
csv << array_row(t.section.complexity.row.file_count_threshold(file_line_count_threshold), format_ncss(metrics_ncss))
|
38
|
+
metrics_ccn = @complexity_report.file_metrics_sorted_by_ccn
|
39
|
+
csv << value_row(t.section.complexity.row.ccn_line_count, @complexity_report.file_measure.sum_ccn, format_ccn(metrics_ccn))
|
40
|
+
# Resources
|
41
|
+
csv << section(t.section.resources.title)
|
42
|
+
csv << array_row(t.section.resources.row.xib_count, @project_report.xibs)
|
43
|
+
csv << array_row(t.section.resources.row.storyboards, @project_report.storyboards)
|
44
|
+
# Tests
|
45
|
+
csv << section(t.section.tests.title)
|
46
|
+
csv << array_row(t.section.tests.row.unit_tests, @project_report.unit_test_target_files)
|
47
|
+
csv << array_row(t.section.tests.row.ui_tests, @project_report.ui_test_target_files)
|
48
|
+
end
|
49
|
+
puts "Report #{url} created 🎉".colorize(:green)
|
50
|
+
end
|
51
|
+
|
52
|
+
def section(title)
|
53
|
+
return [title]
|
54
|
+
end
|
55
|
+
|
56
|
+
def value_row(title, value, detail=[])
|
57
|
+
if value.nil?
|
58
|
+
return format_row [title, detail]
|
59
|
+
end
|
60
|
+
format_row [title, value, format_array(detail)]
|
61
|
+
end
|
62
|
+
|
63
|
+
def array_row(title, array)
|
64
|
+
if array.nil?
|
65
|
+
return [title, 0]
|
66
|
+
end
|
67
|
+
return format_row [title, array.count, format_array(array)]
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_row(array)
|
71
|
+
if @options[:output_format][:adds_row_padding]
|
72
|
+
array.unshift("")
|
73
|
+
end
|
74
|
+
array
|
75
|
+
end
|
76
|
+
|
77
|
+
def format_array(array)
|
78
|
+
size = @options[:output_format][:size]
|
79
|
+
output = array.first(size).join("\n")
|
80
|
+
if array.count > size
|
81
|
+
output += "\n..."
|
82
|
+
end
|
83
|
+
output
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_ncss(metrics)
|
87
|
+
metrics.map { |metric| "(#{metric.ncss}) #{metric.file_url}" }
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_ccn(metrics)
|
91
|
+
metrics.map { |metric| "(#{metric.ccn}) #{metric.file_url}" }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module Review
|
2
|
+
class FunctionMetric
|
3
|
+
|
4
|
+
attr_accessor :file_url, :function_name, :line_number, :ncss, :ccn
|
5
|
+
|
6
|
+
def initialize(file_url = "", function_name = "", line_number = 0, ncss = 0, ccn = 0)
|
7
|
+
@file_url = file_url
|
8
|
+
@function_name = function_name
|
9
|
+
@line_number = line_number
|
10
|
+
@ncss = ncss
|
11
|
+
@ccn = ccn
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FileMetric
|
16
|
+
|
17
|
+
attr_accessor :file_url, :ncss, :ccn, :functions
|
18
|
+
|
19
|
+
def initialize(file_url = "", ncss = 0, ccn = 0, functions = 0)
|
20
|
+
@file_url = file_url
|
21
|
+
@ncss = ncss
|
22
|
+
@ccn = ccn
|
23
|
+
@functions = functions
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class FunctionMeasure
|
28
|
+
|
29
|
+
attr_accessor :metrics, :average_ncss, :average_ccn
|
30
|
+
|
31
|
+
def initialize(metrics = [], average_ncss = 0, average_ccn = 0)
|
32
|
+
@metrics = metrics
|
33
|
+
@average_ncss = average_ncss
|
34
|
+
@average_ccn = average_ccn
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class FileMeasure
|
39
|
+
|
40
|
+
attr_accessor :metrics, :average_ncss, :average_ccn, :average_functions, :sum_ncss, :sum_ccn, :sum_functions
|
41
|
+
|
42
|
+
def initialize(metrics = [], average_ncss = 0, average_ccn = 0, average_functions = 0, sum_ncss = 0, sum_ccn = 0, sum_functions = 0)
|
43
|
+
@metrics = metrics
|
44
|
+
@average_ncss = average_ncss
|
45
|
+
@average_ccn = average_ccn
|
46
|
+
@average_functions = average_functions
|
47
|
+
@sum_ncss = sum_ncss
|
48
|
+
@sum_ccn = sum_ccn
|
49
|
+
@sum_functions = sum_functions
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ComplexityReport
|
54
|
+
|
55
|
+
attr_accessor :function_measure, :file_measure
|
56
|
+
|
57
|
+
def initialize(function_measure = [], file_measure = [])
|
58
|
+
@function_measure = function_measure
|
59
|
+
@file_measure = file_measure
|
60
|
+
end
|
61
|
+
|
62
|
+
def file_metrics_with_more_than_count_lines(count)
|
63
|
+
file_measure.metrics.select { |metric| metric.ncss >= count }.sort { |lhs, rhs| (lhs.ncss <=> rhs.ncss) * -1 }
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_metrics_sorted_by_ccn
|
67
|
+
file_measure.metrics.sort { |lhs, rhs| (lhs.ccn <=> rhs.ccn) * -1 }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
include REXML
|
2
|
+
|
3
|
+
module Review
|
4
|
+
class ComplexityReportParser
|
5
|
+
|
6
|
+
def initialize()
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse_file(url)
|
10
|
+
file = File.new url
|
11
|
+
doc = Document.new file
|
12
|
+
|
13
|
+
function_measure = FunctionMeasure.new
|
14
|
+
file_measure = FileMeasure.new
|
15
|
+
|
16
|
+
function_xml = doc.root.elements["measure[@type='Function']"]
|
17
|
+
function_measure.average_ncss = function_xml.elements["average[@label='NCSS']"]["value"].to_s.to_f
|
18
|
+
function_measure.average_ccn = function_xml.elements["average[@label='CCN']"]["value"].to_s.to_f
|
19
|
+
function_xml.elements.each("item") do |item|
|
20
|
+
values = item.get_elements("value")
|
21
|
+
item_name = item.attributes["name"]
|
22
|
+
metric = FunctionMetric.new
|
23
|
+
metric.file_url = /(?<=( at )).+(?=:)/.match(item_name).to_s
|
24
|
+
metric.function_name = /.+(?=\(...\))/.match(item_name).to_s
|
25
|
+
metric.line_number = /(?<=:)\d+/.match(item_name).to_s.to_i
|
26
|
+
metric.ncss = values[1].text.to_i
|
27
|
+
metric.ccn = values[2].text.to_i
|
28
|
+
function_measure.metrics.push metric
|
29
|
+
end
|
30
|
+
|
31
|
+
file_xml = doc.root.elements["measure[@type='File']"]
|
32
|
+
file_measure.average_ncss = file_xml.elements["average[@label='NCSS']"]["value"].to_s.to_f
|
33
|
+
file_measure.average_ccn = file_xml.elements["average[@label='CCN']"]["value"].to_s.to_f
|
34
|
+
file_measure.average_functions = file_xml.elements["average[@label='Functions']"]["value"].to_s.to_f
|
35
|
+
file_measure.sum_ncss = file_xml.elements["sum[@label='NCSS']"]["value"].to_s.to_i
|
36
|
+
file_measure.sum_ccn = file_xml.elements["sum[@label='CCN']"]["value"].to_s.to_i
|
37
|
+
file_measure.sum_functions = file_xml.elements["sum[@label='Functions']"]["value"].to_s.to_i
|
38
|
+
file_xml.elements.each("item") do |item|
|
39
|
+
values = item.get_elements("value")
|
40
|
+
metric = FileMetric.new
|
41
|
+
metric.file_url = item.attributes["name"].to_s
|
42
|
+
metric.ncss = values[1].text.to_i
|
43
|
+
metric.ccn = values[2].text.to_i
|
44
|
+
metric.functions = values[3].text.to_i
|
45
|
+
file_measure.metrics.push metric
|
46
|
+
end
|
47
|
+
ComplexityReport.new function_measure, file_measure
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Review
|
2
|
+
class ComplexityReviewer
|
3
|
+
|
4
|
+
def initialize()
|
5
|
+
end
|
6
|
+
|
7
|
+
def review_folder(options)
|
8
|
+
puts "Reviewing source complexity... "
|
9
|
+
lizard_report = "lizard-report.xml"
|
10
|
+
source = options[:sources_url]
|
11
|
+
`lizard -m --xml #{source} > #{lizard_report}`
|
12
|
+
parser = ComplexityReportParser.new
|
13
|
+
report = parser.parse_file lizard_report
|
14
|
+
FileUtils.rm lizard_report
|
15
|
+
report
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Review
|
2
|
+
class ProjectReport
|
3
|
+
attr_accessor :name, :version, :deployment_target, :target_names, :configuration_names, :main_target_name, :main_target_files, :main_target_resources, :ui_test_target_name, :unit_test_target_name, :ui_test_target_files, :unit_test_target_files, :localizations
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@name = ""
|
7
|
+
@version = ""
|
8
|
+
@deployment_target = ""
|
9
|
+
@target_names = ""
|
10
|
+
@configuration_names = ""
|
11
|
+
@main_target_name = ""
|
12
|
+
@main_target_files = []
|
13
|
+
@main_target_resources = []
|
14
|
+
@ui_test_target_name = ""
|
15
|
+
@unit_test_targe_name = ""
|
16
|
+
@ui_test_target_files = []
|
17
|
+
@unit_test_target_files = []
|
18
|
+
@localizations = []
|
19
|
+
end
|
20
|
+
|
21
|
+
def main_target_swift_files
|
22
|
+
main_target_files.select { |file| file.include?(".swift") }
|
23
|
+
end
|
24
|
+
|
25
|
+
def main_target_obj_c_files
|
26
|
+
main_target_files.select { |file| file.include?(".m") }
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_swift_files?
|
30
|
+
main_target_swift_files.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_obj_c_files?
|
34
|
+
main_target_obj_c_files.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def xibs
|
38
|
+
main_target_resources.select { |file| file.include?(".xib") }
|
39
|
+
end
|
40
|
+
|
41
|
+
def storyboards
|
42
|
+
main_target_resources.select { |file| file.include?(".storyboard") }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Review
|
2
|
+
class ProjectReviewer
|
3
|
+
|
4
|
+
def initialize()
|
5
|
+
end
|
6
|
+
|
7
|
+
def review_folder(options)
|
8
|
+
file_seeker = Helper::FileSeeker.new
|
9
|
+
url = options[:project_url]
|
10
|
+
project_name = options[:xcodeproj][:name]
|
11
|
+
urls = []
|
12
|
+
if project_name.empty?
|
13
|
+
urls = file_seeker.find_files_with_extension url, "xcodeproj"
|
14
|
+
else
|
15
|
+
urls = file_seeker.find_files url, project_name, "xcodeproj"
|
16
|
+
end
|
17
|
+
if urls.empty?
|
18
|
+
raise "Xcode project not found"
|
19
|
+
end
|
20
|
+
report = ProjectReport.new
|
21
|
+
project = Xcodeproj::Project.open urls.first
|
22
|
+
project_basename = File.basename(urls.first)
|
23
|
+
puts "Reviewing #{project_basename}..."
|
24
|
+
root_object = project.root_object
|
25
|
+
if root_object.targets.empty?
|
26
|
+
raise "Xcode project #{root_object.name} has no target"
|
27
|
+
end
|
28
|
+
report.name = root_object.name
|
29
|
+
report.deployment_target = root_object.targets.map { |target| target.deployment_target }.uniq
|
30
|
+
report.target_names = root_object.targets.map &:name
|
31
|
+
report.configuration_names = root_object.targets.flat_map { |target| target.build_configurations.map(&:name) }.uniq
|
32
|
+
report.localizations = findLocalizations project
|
33
|
+
main_target_name = options[:xcodeproj][:main_target_name]
|
34
|
+
main_target = nil
|
35
|
+
if main_target_name.empty?
|
36
|
+
main_target = root_object.targets[0]
|
37
|
+
else
|
38
|
+
main_target = root_object.targets.detect { |target| target.name == main_target_name }
|
39
|
+
if main_target.nil?
|
40
|
+
raise "Xcode target named #{main_target_name} not found"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
report.version = project.build_configurations.map { |config| config.build_settings["CURRENT_PROJECT_VERSION"] }.uniq.first
|
44
|
+
ui_test_target = root_object.targets.detect { |target| target.build_configurations.any? { |config| !config.build_settings["TEST_TARGET_NAME"].nil? } }
|
45
|
+
unit_test_target = root_object.targets.detect { |target| target.build_configurations.any? { |config| !config.build_settings["TEST_HOST"].nil? } }
|
46
|
+
report.main_target_name = main_target&.name
|
47
|
+
report.main_target_files = main_target&.source_build_phase&.files&.map { |file| file.display_name }
|
48
|
+
report.main_target_resources = main_target&.resources_build_phase&.files&.map { |file| file.display_name }
|
49
|
+
report.ui_test_target_name = ui_test_target&.name
|
50
|
+
report.unit_test_target_name = unit_test_target&.name
|
51
|
+
report.ui_test_target_files = ui_test_target&.source_build_phase&.files&.map { |file| file.display_name }
|
52
|
+
report.unit_test_target_files = unit_test_target&.source_build_phase&.files&.map { |file| file.display_name }
|
53
|
+
return report
|
54
|
+
end
|
55
|
+
|
56
|
+
def findLocalizations(project)
|
57
|
+
files = project.files.select { |f| f.path.include?("Localizable.strings") }
|
58
|
+
files.map { |f| f.name.nil? ? "base" : f.name }.uniq
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: iosappaudit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gaétan Zanella
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: xcodeproj
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: r18n-core
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: colorize
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: byebug
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
description: Audit
|
70
|
+
email: gaetan.zanella@gmail.com
|
71
|
+
executables:
|
72
|
+
- iosappaudit
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- Gemfile
|
78
|
+
- Rakefile
|
79
|
+
- bin/console
|
80
|
+
- bin/setup
|
81
|
+
- default_configuration.yaml
|
82
|
+
- exe/iosappaudit
|
83
|
+
- i18n/en.yml
|
84
|
+
- iosappaudit.gemspec
|
85
|
+
- lib/iosappaudit.rb
|
86
|
+
- lib/iosappaudit/Helper/file_seeker.rb
|
87
|
+
- lib/iosappaudit/Helper/options_parser.rb
|
88
|
+
- lib/iosappaudit/Presenter/CSVReportPresenter.rb
|
89
|
+
- lib/iosappaudit/Review/complexity_report.rb
|
90
|
+
- lib/iosappaudit/Review/complexity_report_parser.rb
|
91
|
+
- lib/iosappaudit/Review/complexity_reviewer.rb
|
92
|
+
- lib/iosappaudit/Review/project_report.rb
|
93
|
+
- lib/iosappaudit/Review/project_reviewer.rb
|
94
|
+
- lib/iosappaudit/version.rb
|
95
|
+
homepage: http://rubygems.org/gems/hola
|
96
|
+
licenses:
|
97
|
+
- MIT
|
98
|
+
metadata:
|
99
|
+
allowed_push_host: https://rubygems.org
|
100
|
+
homepage_uri: http://rubygems.org/gems/hola
|
101
|
+
source_code_uri: https://github.com/faberNovel/iosappaudit
|
102
|
+
changelog_uri: https://github.com/faberNovel/iosappaudit/blob/master/CHANGELOG.md
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubygems_version: 3.0.3
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Audit
|
122
|
+
test_files: []
|