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 +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
|
+
[][travis]
|
4
|
+
[][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
|
+

|
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
|
+

|
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: []
|