swoop_report 0.2.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 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: []