swoop_report 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.coveralls.yml +1 -0
- data/.gitignore +11 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +156 -0
- data/Rakefile +6 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/bin/swoop +6 -0
- data/lib/swoop.rb +93 -0
- data/lib/swoop/entity.rb +44 -0
- data/lib/swoop/entity_parser.rb +82 -0
- data/lib/swoop/project.rb +49 -0
- data/lib/swoop/renderer/chart_renderer.rb +63 -0
- data/lib/swoop/renderer/chart_renderer/chart.html.erb +68 -0
- data/lib/swoop/renderer/csv_renderer.rb +36 -0
- data/lib/swoop/renderer/renderer.rb +16 -0
- data/lib/swoop/renderer/table_renderer.rb +24 -0
- data/lib/swoop/report.rb +145 -0
- data/lib/swoop/time_machine.rb +79 -0
- data/lib/swoop/version.rb +3 -0
- data/screenshots/chart.png +0 -0
- data/screenshots/table.png +0 -0
- data/swoop.gemspec +30 -0
- metadata +196 -0
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
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
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
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
data/bin/swoop
ADDED
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
|
data/lib/swoop/entity.rb
ADDED
@@ -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,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
|
data/lib/swoop/report.rb
ADDED
@@ -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
|
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: []
|