swoop_report 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 070524d26b6ed02ecdb62a8d6210872b0e0fc37b
4
+ data.tar.gz: 444bf5cc8c55ea623c966a9dd3ca1ceb467af177
5
+ SHA512:
6
+ metadata.gz: d69f08bc1efa533450c7722d49a8ffa1f9f69bd746a6b6ee632b8954fc6acd0fda3b4004cc0b66badb74539a8b9bf8a0b6d5dd3a402408f0cbe021d391f3204a
7
+ data.tar.gz: 78344f889e687955331540459bb5cd7f6c401f4c326e2a4cc810a76483c36f8119a588071b87c31d72a8a11984975bc0d1492e230bdb4bc77271443ee6bb35eb
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service_name: travis-ci
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .DS_Store
11
+ html/*
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,8 @@
1
+ osx_image: xcode7.3
2
+ language: objective-c
3
+ rvm: 2.3.1
4
+ before_install: sudo gem install bundler -v 1.11.2
5
+ script: bundle exec rake
6
+ branches:
7
+ only:
8
+ - master
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in swoop.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Ikhsan Assaat
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # Swoop : Swift to Objective-C conversion reporter
2
+
3
+ [![Build Status](http://img.shields.io/travis/ikhsan/swoop/master.svg)][travis]
4
+ [![Coverage Status](http://img.shields.io/coveralls/ikhsan/swoop/master.svg)][coveralls]
5
+
6
+ [travis]: https://travis-ci.org/ikhsan/swoop
7
+ [coveralls]: https://coveralls.io/github/ikhsan/swoop?branch=master
8
+
9
+ Track your swift code in your Xcode codebase through time. It can go back in time from your git repository and make a comparison report.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ gem 'swoop'
17
+ ```
18
+
19
+ And then execute:
20
+
21
+ $ bundle
22
+
23
+ Or install it yourself as:
24
+
25
+ $ gem install swoop
26
+
27
+ ## Usage
28
+
29
+ To print the comparison report inside your console, just run `swoop` command with two required options:
30
+
31
+ ```bash
32
+ $ swoop --path <path> --dir <dir>
33
+ ```
34
+
35
+ - `path` is your path to your `.xcodeproj` and
36
+ - `dir` is your directory inside your Xcode project
37
+
38
+ As default options, this will list the last 8 tags, collect information from each tag, collate it into a report, and then present it as a table inside your console.
39
+
40
+ ### Example
41
+
42
+ Say you have your awesome project inside `YourAwesomeProject` folder called `AwesomeProject.xcodeproj`. You open the project, and you have organised your files in Xcode project navigator to look something like this :
43
+
44
+ ```
45
+ + AwesomeProject
46
+ +- Classes
47
+ +--- Model
48
+ +--- View
49
+ +--- Controller
50
+ +--- Network
51
+ +- Assets
52
+ +- Frameworks
53
+ +- Products
54
+ ```
55
+
56
+ Say, you want to compare all of the classes then run :
57
+
58
+ ```bash
59
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes'
60
+ ```
61
+
62
+ Or, if you want to compare only files inside `Controller`, run :
63
+
64
+ ```bash
65
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes/Controller'
66
+ ```
67
+
68
+ ### Options
69
+
70
+ #### Path (`--path`)
71
+
72
+ Specify the path to the `.xcodeproj` file. (This is required)
73
+
74
+ #### Directory (`--dir`)
75
+
76
+ Specify which directory where the files that you want to compare based on Xcode project navigator. (This is required)
77
+
78
+ #### Tags (`--tags`)
79
+
80
+ Specify a number of how many tags you want to include for comparison. For example, if you want to include the last 10 tags you would run:
81
+
82
+ ```bash
83
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes' --tags 10
84
+ ```
85
+
86
+ #### Filter Tag (`--filter_tag`)
87
+
88
+ Specify a regular expression that will be applied to tag names. This only applies if tags is used. For example, if
89
+
90
+ #### Weeks (`--weeks`)
91
+
92
+ Specify a number of how many weeks you want to include for comparison. For example, if you want to include the last 30 weeks, run :
93
+
94
+ ```bash
95
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes' --weeks 30
96
+ ```
97
+
98
+ #### Renderer (`--render`)
99
+
100
+ Specify how do you render the reports. Available renderers are `table`, `csv` and `chart`
101
+
102
+ ##### Table
103
+
104
+ This renders a table in your console. Table is used if `--render` is not specified.
105
+
106
+ ```bash
107
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes'
108
+ # or
109
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes' --render table
110
+ ```
111
+
112
+ will output this table in your console
113
+
114
+ ![table](/screenshots/table.png?raw=true)
115
+
116
+
117
+ ##### CSV
118
+
119
+ This will export your report as a csv file in root.
120
+
121
+ ```bash
122
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes' --render csv
123
+ ```
124
+
125
+ ##### Chart
126
+
127
+ This will export your report as a chart in a webpage. It creates an `html` folder in root with the page `index.html` inside of it.
128
+
129
+ ```bash
130
+ $ swoop --path "~/YourAwesomeProject/AwesomeProject.xcodeproj" --dir 'Classes' --render chart
131
+ ```
132
+
133
+ will output a chart in `./html/index.html`
134
+
135
+ ![chart](/screenshots/chart.png?raw=true)
136
+
137
+ ## Contributing
138
+
139
+ Feel free to put any ideas, questions or bug reports by [creating issues](https://github.com/ikhsan/swoop/issues/new). If you think you have a great idea for any improvement we'd be really happy to receive any pull request (refer to **Development** section to setup in your local machine).
140
+
141
+ ### Development
142
+
143
+ Clone this repository and get into the directory.
144
+
145
+ ```bash
146
+ # Install dependencies
147
+ $ bin/setup
148
+ # Run the tests
149
+ $ rake spec
150
+ # (Optional) run interactive console for experimentation
151
+ $ bin/console
152
+ ```
153
+
154
+ ## License
155
+
156
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+
5
+ # You can add fixtures and/or initialization code here to make experimenting
6
+ # with your gem easier. You can also use a different console, if you like.
7
+
8
+ # (If you use this, don't forget to add pry to your Gemfile!)
9
+ # require "pry"
10
+ # Pry.start
11
+
12
+ require "irb"
13
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/bin/swoop ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "swoop"
5
+
6
+ Swoop::Reporter.start(ARGV)
data/lib/swoop.rb ADDED
@@ -0,0 +1,93 @@
1
+ require "swoop/version"
2
+ require "swoop/project"
3
+ require "swoop/entity"
4
+ require "swoop/entity_parser"
5
+ require "swoop/report"
6
+ require "swoop/time_machine"
7
+
8
+ require "swoop/renderer/renderer"
9
+ require "swoop/renderer/csv_renderer"
10
+ require "swoop/renderer/table_renderer"
11
+ require "swoop/renderer/chart_renderer"
12
+
13
+ require "thor"
14
+
15
+ module Swoop
16
+
17
+ class Reporter < Thor
18
+ desc "report", "Create comparison report from Swift and Objective-C files inside your Xcode project"
19
+ long_desc <<-LONGDESC
20
+ `swoop` will make Swift and Objective-C comparison report out of your files inside an Xcode project.
21
+
22
+ It requires two required options, `path` for specifying your Xcode project's path and `dir` to specify Xcode directory from your project.
23
+
24
+ > $ swoop --path ~/YourAwesomeProject/AwesomeProject.xcodeproj --dir 'Classes/Network'
25
+
26
+ There are three supported renderer that can be choosen using `renderer` option; `table` (default), `csv` and `chart`.
27
+
28
+ > $ swoop --path ~/YourAwesomeProject/AwesomeProject.xcodeproj --dir 'Classes' --render chart
29
+ LONGDESC
30
+ option :path, :required => true, :desc => "Specify your .xcodeproj path"
31
+ option :dir, :required => true, :desc => "Specify your folder from your Xcode project"
32
+ option :tags, :desc => "Specify how many tags to include (if not specified then it defaults to 8 latest tags)"
33
+ option :filter_tag, :desc => "Regular expression for filtering tags format"
34
+ option :weeks, :desc => "Specify how many weeks to include"
35
+ option :render, :desc => "Choose your renderer, if not specified will default to `table`", :banner => "['table', 'csv', 'chart']"
36
+ def report
37
+ @project_path = options[:path]
38
+ @dir_path = options[:dir]
39
+ @renderer_type = options[:render]
40
+
41
+ @filter_tag = options[:filter_tag]
42
+ @tags = options[:tags]
43
+ @weeks = options[:weeks]
44
+
45
+ renderer = renderer_class.new(summary_report, title)
46
+ renderer.render
47
+ end
48
+
49
+ default_task :report
50
+
51
+ private
52
+
53
+ def summary_report
54
+ @summary_report ||= begin
55
+ reports = []
56
+
57
+ project = Project.new(@project_path, @dir_path)
58
+ delorean = TimeMachine.new(project, time_machine_options)
59
+ delorean.travel do |proj, name, date|
60
+ entities = EntityParser.parse_files(proj.filepaths)
61
+ reports << Report.new(entities, name, date)
62
+ end
63
+
64
+ reports
65
+ end
66
+ end
67
+
68
+
69
+ def time_machine_options
70
+ options = {}
71
+ options[:tags] = @tags.to_i unless @tags.nil? || @tags.empty?
72
+ options[:filter] = @filter_tag unless @filter_tag.nil? || @filter_tag.empty?
73
+ options[:weeks] = @weeks.to_i unless @weeks.nil? || @weeks.empty?
74
+ options
75
+ end
76
+
77
+ def title
78
+ "Swoop Report : '#{@dir_path}'"
79
+ end
80
+
81
+ def renderer_class
82
+ case @renderer_type
83
+ when "csv"
84
+ CSVRenderer
85
+ when "chart"
86
+ ChartRenderer
87
+ else
88
+ TableRenderer
89
+ end
90
+ end
91
+ end
92
+
93
+ end
@@ -0,0 +1,44 @@
1
+ module Swoop
2
+ class Entity
3
+ attr_reader :name, :language, :type
4
+
5
+ def initialize(name, language, type)
6
+ @name = name
7
+ @language = language
8
+ @type = type
9
+ end
10
+
11
+ def swift?
12
+ language == "swift"
13
+ end
14
+
15
+ def objc?
16
+ language == "objc"
17
+ end
18
+
19
+ def class?
20
+ type == "class"
21
+ end
22
+
23
+ def struct?
24
+ type == "struct"
25
+ end
26
+
27
+ def extension?
28
+ type == "extension" || type == "category"
29
+ end
30
+
31
+ def to_s
32
+ "#{language} - #{name} : #{type}"
33
+ end
34
+
35
+ def ==(other)
36
+ name == other.name && language == other.language && type == other.type
37
+ end
38
+ alias :eql? :==
39
+
40
+ def hash
41
+ [name, language, type].hash
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,82 @@
1
+ require 'json'
2
+
3
+ module Swoop
4
+
5
+ class EntityParser
6
+
7
+ attr_reader :entities
8
+
9
+ def self.parse_files(filepaths)
10
+ filepaths
11
+ .map { |p| self.new(p).entities }
12
+ .flatten
13
+ .uniq
14
+ end
15
+
16
+ def initialize(filepath)
17
+ @filepath = filepath
18
+ end
19
+
20
+ def entities
21
+ @entitites ||= begin
22
+ return parse_swift if File.extname(@filepath) == ".swift"
23
+ return parse_objc if File.extname(@filepath) == ".h"
24
+ []
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ LANG_SWIFT = 'swift'
31
+ LANG_OBJC = 'objc'
32
+ TYPE_CLASS = 'class'
33
+ TYPE_STRUCT = 'struct'
34
+ TYPE_EXTENSION = 'extension'
35
+ TYPE_CATEGORY = 'category'
36
+
37
+ SWIFT_CLASS = 'class\s*(\w+)'
38
+ SWIFT_STRUCT = 'struct\s*(\w+)'
39
+ SWIFT_EXT = 'extension\s*(\w+)'
40
+ def parse_swift
41
+ classes = filter(SWIFT_CLASS).map { |e| Entity.new(e, LANG_SWIFT, TYPE_CLASS) }
42
+ structs = filter(SWIFT_STRUCT).map { |e| Entity.new(e, LANG_SWIFT, TYPE_STRUCT) }
43
+ extensions = filter(SWIFT_EXT).map { |e| Entity.new(e, LANG_SWIFT, TYPE_EXTENSION) }
44
+
45
+ [ *classes, *structs, *extensions ]
46
+ end
47
+
48
+ OBJC_CLASS = '@interface\s*(\w+)\s*:'
49
+ OBJC_CATEGORY = '@interface\s*(\w+)\s*\((.*)\)'
50
+ OBJC_STRUCT ='typedef\s*struct\s*{.*?}\s*(\w*)'
51
+ OBJC_STRUCT2 = 'struct\s*(\w+)'
52
+ def parse_objc
53
+ classes = filter(OBJC_CLASS).map { |e| Entity.new(e, LANG_OBJC, TYPE_CLASS) }
54
+ categories = filter(OBJC_CATEGORY, false)
55
+ .map { |exts|
56
+ exts.select { |e| !e.empty? }.join('+')
57
+ }
58
+ .map { |e| Entity.new(e, LANG_OBJC, (e.include?('+') ? TYPE_CATEGORY : TYPE_EXTENSION)) }
59
+ structs = (filter(OBJC_STRUCT, true, Regexp::MULTILINE) + filter(OBJC_STRUCT2))
60
+ .map { |e| Entity.new(e, LANG_OBJC, TYPE_STRUCT) }
61
+
62
+ [ *classes, *structs, *categories ]
63
+ end
64
+
65
+ def file_content
66
+ @file_content ||= File.read(@filepath)
67
+ end
68
+
69
+ def filter(expression, should_flatten = true, options = 0)
70
+ # filter out comments
71
+ regex = Regexp.new("#{expression}", options)
72
+ contents = file_content.scan(regex)
73
+ regex_comments = Regexp.new(".*\/\/.*#{expression}", options)
74
+ commented = file_content.scan(regex_comments)
75
+
76
+ filtered = contents - commented
77
+ return filtered unless should_flatten
78
+ filtered.flatten
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,49 @@
1
+ require 'xcodeproj'
2
+
3
+ module Swoop
4
+
5
+ class Project
6
+
7
+ attr_reader :path, :directory
8
+
9
+ def initialize(path, directory = 'Classes')
10
+ @path = path
11
+ @directory = directory
12
+ end
13
+
14
+ def filepaths
15
+ raise "Error: Project path is empty :(" if @path.empty?
16
+ raise "Error: Can't find .xcodeproj project :(" unless File.exist? @path
17
+ raise "Error: Invalid .xcodeproj file :(" unless File.extname(@path) == '.xcodeproj'
18
+
19
+ project = Xcodeproj::Project.open @path
20
+ project_dir = project[directory]
21
+
22
+ raise "Error: Can't find directory :(" if project_dir.nil?
23
+
24
+ filepaths = project_dir
25
+ .recursive_children
26
+ .select { |f| valid_file? f }
27
+ .map(&:real_path)
28
+
29
+ raise "Error: No files are found :(" if filepaths.empty?
30
+
31
+ filepaths
32
+ end
33
+
34
+ private
35
+
36
+ def valid_file_extensions
37
+ [".swift", ".h", ".m"]
38
+ end
39
+
40
+ def valid_file? (f)
41
+ return false unless f.is_a?(Xcodeproj::Project::Object::PBXFileReference)
42
+
43
+ ext = File.extname(f.real_path)
44
+ valid_file_extensions.include?(ext)
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,63 @@
1
+ require 'erb'
2
+
3
+ module Swoop
4
+
5
+ class ChartRenderer < Renderer
6
+
7
+ attr_reader :filename
8
+
9
+ def initialize(reports, title, filename = "index.html")
10
+ super(reports, title)
11
+ @filename = filename
12
+ end
13
+
14
+ def class_count_data
15
+ rows = []
16
+ rows << ['Date', 'Swift Class', 'Objective-C Class']
17
+ rows += reports.map { |r| [ "#{r.name}\n(#{r.date})", r.swift_classes_count, r.objc_classes_count] }
18
+ rows.to_s
19
+ end
20
+
21
+ def class_percentage_data
22
+ rows = []
23
+ rows << ['Date', 'Swift Class (%)', 'Objective-C Class (%)']
24
+ rows += reports.map { |r| [ "#{r.name}\n(#{r.date})", r.swift_classes_percentage, r.objc_classes_percentage] }
25
+ rows.to_s
26
+ end
27
+
28
+ def render
29
+ empty_target_dir
30
+ html_path = render_html
31
+ `open #{html_path}`
32
+ end
33
+
34
+ private
35
+
36
+ def source_dir
37
+ @source_dir ||= File.expand_path("./lib/swoop/renderer/chart_renderer")
38
+ end
39
+
40
+ def target_dir
41
+ @target_dir ||= File.expand_path("./html")
42
+ end
43
+
44
+ def empty_target_dir
45
+ FileUtils.rm_rf(target_dir) if Dir.exist?(target_dir)
46
+ FileUtils.mkdir_p(target_dir)
47
+ end
48
+
49
+ def render_html
50
+ template_path = File.join(source_dir, "chart.html.erb")
51
+ template = ERB.new(File.read(template_path))
52
+ html_content = template.result(binding)
53
+
54
+ html_path = File.join(target_dir, filename)
55
+ File.open(html_path, "w") do |file|
56
+ file.puts html_content
57
+ end
58
+
59
+ html_path
60
+ end
61
+ end
62
+
63
+ end
@@ -0,0 +1,68 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title><%= title %></title>
6
+ <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
7
+ <style media="screen">
8
+ body {
9
+ font: 12px sans-serif;
10
+ }
11
+
12
+ .flex-container {
13
+ padding: 0;
14
+ margin: 0;
15
+ display: flex;
16
+ justify-content: space-around;
17
+ }
18
+
19
+ .flex-item {
20
+ width: 850px;
21
+ height: 450px;
22
+ }
23
+ </style>
24
+ </head>
25
+ <body>
26
+ <h2>SWOOP : Swift x Objective-C Comparison Report</h2>
27
+ <h4><%= title %></h4>
28
+
29
+ <div class="flex-container">
30
+ <div id="class_count" class="flex-item"></div>
31
+ <div id="class_percentage" class="flex-item"></div>
32
+ </div>
33
+
34
+ <script type="text/javascript">
35
+ google.charts.load('current', {'packages':['corechart']});
36
+ google.charts.setOnLoadCallback(drawCharts);
37
+
38
+ function drawCharts() {
39
+ drawClassCountChart();
40
+ drawClassCountPercentage();
41
+ }
42
+
43
+ function drawClassCountChart() {
44
+ var data = google.visualization.arrayToDataTable(<%= class_count_data %>);
45
+ var options = {
46
+ title: 'Classes Count',
47
+ hAxis: {title: 'Version', titleTextStyle: {color: '#333'}},
48
+ vAxis: {minValue: 0},
49
+ isStacked: true
50
+ };
51
+ var chart = new google.visualization.AreaChart(document.getElementById('class_count'));
52
+ chart.draw(data, options);
53
+ }
54
+
55
+ function drawClassCountPercentage() {
56
+ var data = google.visualization.arrayToDataTable(<%= class_percentage_data %>);
57
+ var options = {
58
+ title: 'Classes Percentage',
59
+ hAxis: {title: 'Version', titleTextStyle: {color: '#333'}},
60
+ vAxis: {minValue: 0},
61
+ isStacked: 'percent'
62
+ };
63
+ var chart = new google.visualization.AreaChart(document.getElementById('class_percentage'));
64
+ chart.draw(data, options);
65
+ }
66
+ </script>
67
+ </body>
68
+ </html>
@@ -0,0 +1,36 @@
1
+ require 'digest'
2
+
3
+ module Swoop
4
+
5
+ class CSVRenderer < Renderer
6
+
7
+ def render
8
+ File.write(filename, csv)
9
+ puts "CSV file successfully created at '#{filename}'" if File.file?(filename)
10
+ end
11
+
12
+ private
13
+
14
+ def filename
15
+ @filename ||= begin
16
+ reports_name = reports.map { |r| "#{r.name}-#{r.date.to_s}" }.to_s
17
+ sha = Digest::SHA1.hexdigest(reports_name)
18
+ "#{sha}.csv"
19
+ end
20
+ end
21
+
22
+ def csv
23
+ @csv ||= begin
24
+ lines = []
25
+ lines << "name,date,swift class,swift class %,objc class,objc class %,total class,swift struct,swift struct %,objc struct,objc struct %,total struct,swift extension,swift extension %,objc extension,objc extension %,total extension"
26
+
27
+ reports.each do |report|
28
+ lines << "#{report.name},#{report.date},#{report.swift_classes_count},#{'%.02f' % report.swift_classes_percentage},#{report.objc_classes_count},#{'%.02f' % report.objc_classes_percentage},#{report.classes_count},#{report.swift_structs_count},#{'%.02f' % report.swift_structs_percentage},#{report.objc_structs_count},#{'%.02f' % report.objc_structs_percentage},#{report.structs_count},#{report.swift_extensions_count},#{'%.02f' % report.swift_extensions_percentage},#{report.objc_extensions_count},#{'%.02f' % report.objc_extensions_percentage},#{report.extensions_count}"
29
+ end
30
+
31
+ lines.join("\n")
32
+ end
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,16 @@
1
+ module Swoop
2
+
3
+ class Renderer
4
+ attr_reader :reports, :title
5
+
6
+ def initialize(reports, title)
7
+ @reports = reports
8
+ @title = title
9
+ end
10
+
11
+ def render
12
+ # should be implemented by subclass
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'terminal-table'
2
+
3
+ module Swoop
4
+
5
+ class TableRenderer < Renderer
6
+
7
+ def render
8
+ puts table
9
+ end
10
+
11
+ private
12
+
13
+ def table
14
+ headings = ["name", "date", "swift\nclass", "swift\nclass (%)", "objc\nclass", "objc\nclass (%)", "total\nclass", "swift\nstruct", "swift\nstruct(%)", "objc\nstruct", "objc\nstruct(%)", "total\nstruct", "swift\next", "swift\next(%)", "objc\next", "objc\next(%)", "total\next"]
15
+ rows = reports.map do |r|
16
+ [r.name,r.date,r.swift_classes_count,"#{'%.02f' % r.swift_classes_percentage}",r.objc_classes_count,"#{'%.02f' % r.objc_classes_percentage}",r.classes_count,r.swift_structs_count,"#{'%.02f' % r.swift_structs_percentage}",r.objc_structs_count,"#{'%.02f' % r.objc_structs_percentage}",r.structs_count,r.swift_extensions_count,"#{'%.02f' % r.swift_extensions_percentage}",r.objc_extensions_count,"#{'%.02f' % r.objc_extensions_percentage}",r.extensions_count]
17
+ end
18
+
19
+ @table ||= Terminal::Table.new(:title => title, :headings => headings, :rows => rows)
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,145 @@
1
+ module Swoop
2
+
3
+ class Report
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(entities, name = '', date = Time.now)
8
+ @entities = entities
9
+ @date = date
10
+
11
+ if name.nil? || name == ''
12
+ @name = date.strftime("%d-%m-%Y")
13
+ else
14
+ @name = name
15
+ end
16
+ end
17
+
18
+ def date
19
+ @date.strftime("%d-%m-%Y")
20
+ end
21
+
22
+ # Classes
23
+ def classes_count
24
+ classes.count
25
+ end
26
+
27
+ def swift_classes_count
28
+ swift_classes.count
29
+ end
30
+
31
+ def objc_classes_count
32
+ objc_classes.count
33
+ end
34
+
35
+ def swift_classes_percentage
36
+ return 0 if classes_count == 0
37
+ (swift_classes_count.to_f / classes_count) * 100
38
+ end
39
+
40
+ def objc_classes_percentage
41
+ return 0 if classes_count == 0
42
+ (objc_classes_count.to_f / classes_count) * 100
43
+ end
44
+
45
+ # Structs
46
+
47
+ def structs_count
48
+ structs.count
49
+ end
50
+
51
+ def swift_structs_count
52
+ swift_structs.count
53
+ end
54
+
55
+ def objc_structs_count
56
+ objc_structs.count
57
+ end
58
+
59
+ def swift_structs_percentage
60
+ return 0 if structs_count == 0
61
+ (swift_structs_count.to_f / structs_count) * 100
62
+ end
63
+
64
+ def objc_structs_percentage
65
+ return 0 if structs_count == 0
66
+ (objc_structs_count.to_f / structs_count) * 100
67
+ end
68
+
69
+ # Extensions
70
+
71
+ def extensions_count
72
+ extensions.count
73
+ end
74
+
75
+ def swift_extensions_count
76
+ swift_extensions.count
77
+ end
78
+
79
+ def objc_extensions_count
80
+ objc_extensions.count
81
+ end
82
+
83
+ def swift_extensions_percentage
84
+ return 0 if extensions_count == 0
85
+ (swift_extensions_count.to_f / extensions_count) * 100
86
+ end
87
+
88
+ def objc_extensions_percentage
89
+ return 0 if extensions_count == 0
90
+ (objc_extensions_count.to_f / extensions_count) * 100
91
+ end
92
+
93
+ def to_s
94
+ "Class | swift : #{swift_classes_count} (#{'%.02f' % swift_classes_percentage}%) , objc : #{objc_classes_count} (#{'%.02f' % objc_classes_percentage}%), total: #{classes_count} \n" +
95
+ "Structs | swift : #{swift_structs_count} (#{'%.02f' % swift_structs_percentage}%) , objc : #{objc_structs_count} (#{'%.02f' % objc_structs_percentage}%), total: #{structs_count} \n" +
96
+ "Extensions | swift : #{swift_extensions_count} (#{'%.02f' % swift_extensions_percentage}%) , objc : #{objc_extensions_count} (#{'%.02f' % objc_extensions_percentage}%), total: #{extensions_count}"
97
+ end
98
+
99
+ private
100
+
101
+ def classes
102
+ @classes ||= @entities.select(&:class?)
103
+ end
104
+
105
+ def structs
106
+ @structs ||= @entities.select(&:struct?)
107
+ end
108
+
109
+ def extensions
110
+ @extensions ||= @entities.select(&:extension?)
111
+ end
112
+
113
+ def swift_entities
114
+ @swift_entities ||= @entities.select(&:swift?)
115
+ end
116
+
117
+ def swift_classes
118
+ @swift_classes ||= swift_entities.select(&:class?)
119
+ end
120
+
121
+ def swift_structs
122
+ @swift_structs ||= swift_entities.select(&:struct?)
123
+ end
124
+
125
+ def swift_extensions
126
+ @swift_extensions ||= swift_entities.select(&:extension?)
127
+ end
128
+
129
+ def objc_entities
130
+ @objc_entities ||= @entities.select(&:objc?)
131
+ end
132
+
133
+ def objc_classes
134
+ @objc_classes ||= objc_entities.select(&:class?)
135
+ end
136
+
137
+ def objc_structs
138
+ @objc_structs ||= objc_entities.select(&:struct?)
139
+ end
140
+
141
+ def objc_extensions
142
+ @objc_extensions ||= objc_entities.select(&:extension?)
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,79 @@
1
+ require 'git'
2
+
3
+ module Swoop
4
+
5
+ class TimeMachine
6
+
7
+ attr_reader :project
8
+
9
+ def initialize(project, options)
10
+ @project = project
11
+ @tags = options[:tags] || 8
12
+ @weeks = options[:weeks] || 0
13
+ @filter = options[:filter]
14
+ end
15
+
16
+ def project_path
17
+ @project_path ||= @project.path
18
+ end
19
+
20
+ def travel
21
+ current_branch = git.current_branch
22
+
23
+ logs.each do |t|
24
+ log = t.log.first
25
+ git.checkout(log.sha)
26
+ yield(project, t.name, log.date)
27
+ end
28
+
29
+ git.branches[current_branch].checkout
30
+ yield(project, current_branch, Time.now)
31
+ end
32
+
33
+ private
34
+
35
+ def get_git_root
36
+ current_dir = `pwd`
37
+ project_dir = File.dirname(project_path)
38
+
39
+ path = `cd #{project_dir};git rev-parse --show-toplevel;cd #{current_dir}`
40
+ path.strip
41
+ end
42
+
43
+ def git
44
+ @git ||= Git.open(get_git_root)
45
+ end
46
+
47
+ def logs
48
+ return logs_by_week if @weeks > 0
49
+ logs_by_tags
50
+ end
51
+
52
+ def logs_by_tags
53
+ filtered_tags = git.tags
54
+ filtered_tags = filtered_tags.select { |e| e.name.match(@filter) } unless @filter.nil? || @filter.empty?
55
+
56
+ filtered_tags
57
+ .sort { |a, b| a.log.first.date <=> b.log.first.date }
58
+ .last(@tags)
59
+ end
60
+
61
+ def logs_by_week
62
+ log = git.log(5000).since("#{@weeks+1} weeks ago")
63
+
64
+ logs = []
65
+ (0..log.size-1).each { |i| logs << log[i] }
66
+
67
+ logs
68
+ .reduce({}) { |memo, l|
69
+ week = l.date.strftime('%W')
70
+ memo[week] = l
71
+ memo
72
+ }
73
+ .values
74
+ .reverse
75
+ end
76
+
77
+ end
78
+
79
+ end
@@ -0,0 +1,3 @@
1
+ module Swoop
2
+ VERSION = "0.2.0"
3
+ end
Binary file
Binary file
data/swoop.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'swoop/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "swoop_report"
8
+ spec.version = Swoop::VERSION
9
+ spec.authors = ["Ikhsan Assaat"]
10
+ spec.email = ["ikhsan.assaat@songkick.com"]
11
+
12
+ spec.summary = "Compare how swift swoops objective-c classes over time"
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
16
+ spec.bindir = "exe"
17
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.11"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ spec.add_development_dependency "pry", "~> 0.10.3"
24
+ spec.add_development_dependency "coveralls"
25
+
26
+ spec.add_runtime_dependency "thor", "~> 0.19"
27
+ spec.add_runtime_dependency "xcodeproj", "~> 0.28"
28
+ spec.add_runtime_dependency "git", "~> 1.3"
29
+ spec.add_runtime_dependency "terminal-table", "~> 1.5"
30
+ end
metadata ADDED
@@ -0,0 +1,196 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: swoop_report
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Ikhsan Assaat
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.10.3
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.10.3
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: thor
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.19'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.19'
97
+ - !ruby/object:Gem::Dependency
98
+ name: xcodeproj
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.28'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.28'
111
+ - !ruby/object:Gem::Dependency
112
+ name: git
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '1.3'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '1.3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: terminal-table
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.5'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.5'
139
+ description:
140
+ email:
141
+ - ikhsan.assaat@songkick.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".coveralls.yml"
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - ".travis.yml"
150
+ - Gemfile
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - bin/console
155
+ - bin/setup
156
+ - bin/swoop
157
+ - lib/swoop.rb
158
+ - lib/swoop/entity.rb
159
+ - lib/swoop/entity_parser.rb
160
+ - lib/swoop/project.rb
161
+ - lib/swoop/renderer/chart_renderer.rb
162
+ - lib/swoop/renderer/chart_renderer/chart.html.erb
163
+ - lib/swoop/renderer/csv_renderer.rb
164
+ - lib/swoop/renderer/renderer.rb
165
+ - lib/swoop/renderer/table_renderer.rb
166
+ - lib/swoop/report.rb
167
+ - lib/swoop/time_machine.rb
168
+ - lib/swoop/version.rb
169
+ - screenshots/chart.png
170
+ - screenshots/table.png
171
+ - swoop.gemspec
172
+ homepage:
173
+ licenses:
174
+ - MIT
175
+ metadata: {}
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 2.4.8
193
+ signing_key:
194
+ specification_version: 4
195
+ summary: Compare how swift swoops objective-c classes over time
196
+ test_files: []