grenouille 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +14 -0
- data/.swift-version +1 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +22 -0
- data/README.md +67 -0
- data/Rakefile +12 -0
- data/bin/grenouille +24 -0
- data/circle.yml +10 -0
- data/grenouille.gemspec +22 -0
- data/lib/grenouille.rb +3 -0
- data/lib/grenouille/swift_update.rb +56 -0
- data/lib/grenouille/version.rb +3 -0
- data/lib/grenouille/xcode.rb +33 -0
- data/perfume.gif +0 -0
- data/spec/fixtures/Blueprint.swift +365 -0
- data/spec/fixtures/ChoreTask.swift +179 -0
- data/spec/fixtures/Mac-1.1.swift +8 -0
- data/spec/fixtures/Stargate.swift +149 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/swift_update_spec.rb +61 -0
- data/spec/xcode_spec.rb +35 -0
- metadata +103 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5fb6d6bb1e943d840ef1d4b37d6701d5d2c07071
|
4
|
+
data.tar.gz: 0514a9e6533a300b3a602fe81fb5e56d56e59256
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: aae2f47fb581590773f33df6341641a2cbec7f960f4cca7e6e7b7985356173f286c47a0af55588848b3eebe4c164cffa7924c9ee2726a5930999d9e5f380e880
|
7
|
+
data.tar.gz: fec7b7cc4946fe40e95fa3a95235ec44876600fe8b249922ce368b3a60bf6a8b77d5b63d3a0f97280eca687671b2c41ea61d533758670d9bcdec1b3b9ee64e26
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.swift-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.2
|
data/Gemfile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
group :development do
|
6
|
+
gem 'bacon'
|
7
|
+
gem 'mocha-on-bacon'
|
8
|
+
gem 'mocha', '~> 0.11.4'
|
9
|
+
gem 'prettybacon', git: 'git@github.com:irrationalfab/PrettyBacon.git', branch: 'master'
|
10
|
+
gem 'coveralls', require: false
|
11
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Boris Bügling
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Grenouille
|
2
|
+
|
3
|
+
[](https://circleci.com/gh/neonichu/grenouille)
|
4
|
+
[](https://coveralls.io/r/neonichu/grenouille)
|
5
|
+
[](http://badge.fury.io/rb/grenouille)
|
6
|
+
[](https://codeclimate.com/github/neonichu/grenouille)
|
7
|
+
|
8
|
+

|
9
|
+
|
10
|
+
Grenouille uses Apple's `swift-update` tool to determine the version of Swift
|
11
|
+
used in a set of files automatically.
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
You can use it on the commandline:
|
16
|
+
|
17
|
+
```bash
|
18
|
+
$ grenouille Stargate.swift
|
19
|
+
1.2
|
20
|
+
```
|
21
|
+
|
22
|
+
or programmatically:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
su = Grenouille::SwiftUpdate.new
|
26
|
+
su.determine_version('Stargate.swift')
|
27
|
+
# 1.2
|
28
|
+
```
|
29
|
+
|
30
|
+
You can also get more detailled output:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
su.update_to_latest_swift('spec/fixtures/Blueprint.swift')
|
34
|
+
# {:report=>[{"file"=>"spec/fixtures/Blueprint.swift", "offset"=>740, "remove"=>2, "text"=>"as!"}, {"file"=>"spec/fixtures/Blueprint.swift", "offset"=>6758, "remove"=>2, "text"=>"as!"}, {"file"=>"spec/fixtures/Blueprint.swift", "offset"=>10613, "remove"=>2, "text"=>"as!"}], :output=>""}
|
35
|
+
```
|
36
|
+
|
37
|
+
The value for the `:report` key represents the result of `swift-update`, a
|
38
|
+
list of changes to perform to transform the current code to the version of
|
39
|
+
Swift supported by the used Xcode. The value for the `:output` key is the
|
40
|
+
aggregate of stdout and stderr of `swift-update`, e.g. compilation errors.
|
41
|
+
|
42
|
+
## Installation
|
43
|
+
|
44
|
+
Add this line to your application's Gemfile:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
gem 'grenouille'
|
48
|
+
```
|
49
|
+
|
50
|
+
And then execute:
|
51
|
+
|
52
|
+
$ bundle
|
53
|
+
|
54
|
+
Or install it yourself as:
|
55
|
+
|
56
|
+
$ gem install grenouille
|
57
|
+
|
58
|
+
Note: The `swift-update` tool is a part of Xcode since 6.3, so it
|
59
|
+
needs at least that version to function.
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
1. Fork it ( https://github.com/neonichu/grenouille/fork )
|
64
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
66
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
67
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/grenouille
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
if $PROGRAM_NAME == __FILE__
|
4
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
|
5
|
+
require 'rubygems'
|
6
|
+
require 'bundler/setup'
|
7
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'grenouille'
|
11
|
+
|
12
|
+
su = Grenouille::SwiftUpdate.new
|
13
|
+
|
14
|
+
if su.current_version < Gem::Version.new('6.3')
|
15
|
+
puts 'Please install Xcode 6.3 or newer.'
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
puts su.determine_version(ARGV)
|
21
|
+
rescue StandardError => msg
|
22
|
+
puts "Error: #{msg}"
|
23
|
+
exit(1)
|
24
|
+
end
|
data/circle.yml
ADDED
data/grenouille.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'grenouille/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'grenouille'
|
8
|
+
spec.version = Grenouille::VERSION
|
9
|
+
spec.authors = ['Boris Bügling']
|
10
|
+
spec.email = ['boris@buegling.com']
|
11
|
+
spec.summary = 'Automatically detect Swift versions from source code.'
|
12
|
+
spec.homepage = 'https://github.com/neonichu/grenouille'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
21
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
22
|
+
end
|
data/lib/grenouille.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'grenouille/xcode'
|
2
|
+
require 'pathname'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
# check for xcrun: error: unable to find utility "swift-update", not a developer tool or in PATH
|
7
|
+
|
8
|
+
module Grenouille
|
9
|
+
class SwiftUpdate < Xcode
|
10
|
+
def determine_version(file_glob)
|
11
|
+
required_changes = update_to_latest_swift(file_glob)[:report]
|
12
|
+
required_changes.count == 0 ? current_swift_version : previous_swift_version
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_to_latest_swift(file_glob)
|
16
|
+
fail 'Non-Swift files in input' if Dir.glob(file_glob).reject { |f| Pathname.new(f).extname == '.swift' }.count > 0
|
17
|
+
files = Dir.glob(file_glob).join(' ')
|
18
|
+
fail 'No files in input' if files == ''
|
19
|
+
|
20
|
+
platform = guess_platform(file_glob)
|
21
|
+
sdk_path = xcrun("-sdk #{platform} -show-sdk-path").chomp
|
22
|
+
sdk_version = xcrun("-sdk #{platform} -show-sdk-version").chomp
|
23
|
+
target = platform == :iphoneos ? "-target arm64-apple-ios#{sdk_version}" : ''
|
24
|
+
|
25
|
+
output = xcrun("-sdk #{platform} swift-update 2>&1")
|
26
|
+
fail 'Unable to find "swift-update", install Xcode 6.3 or later' if output =~ /unable to find utility/
|
27
|
+
|
28
|
+
temp_file = Tempfile.new('swift-update')
|
29
|
+
begin
|
30
|
+
output = xcrun("-sdk #{platform} swift-update -sdk #{sdk_path} #{files} #{target} -o #{temp_file.path} 2>&1")
|
31
|
+
report = YAML.load(File.read(temp_file))
|
32
|
+
{ report: report, output: output }
|
33
|
+
ensure
|
34
|
+
temp_file.close
|
35
|
+
temp_file.unlink
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def guess_platform(file_glob)
|
42
|
+
Dir.glob(file_glob).each do |file|
|
43
|
+
content = File.read(file)
|
44
|
+
return :macosx if content =~ /^import AppKit$/
|
45
|
+
return :macosx if content =~ /NSTask/
|
46
|
+
end
|
47
|
+
|
48
|
+
:iphoneos
|
49
|
+
end
|
50
|
+
|
51
|
+
def previous_swift_version
|
52
|
+
current = current_swift_version.segments
|
53
|
+
Gem::Version.new("#{current[0]}.#{current[1] - 1}")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rubygems/version'
|
2
|
+
|
3
|
+
module Grenouille
|
4
|
+
class Xcode
|
5
|
+
def initialize(developer_dir = `xcode-select -p`.chomp)
|
6
|
+
@developer_dir = developer_dir
|
7
|
+
end
|
8
|
+
|
9
|
+
def current_version
|
10
|
+
get_version('xcodebuild -version')
|
11
|
+
end
|
12
|
+
|
13
|
+
def current_swift_version
|
14
|
+
get_version('swift -version', 3) || get_version('swift -version', 2)
|
15
|
+
end
|
16
|
+
|
17
|
+
def xcrun(command)
|
18
|
+
`DEVELOPER_DIR=#{@developer_dir} xcrun #{command}`
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def get_version(command, index = 1)
|
24
|
+
version_string = xcrun(command).split(' ')
|
25
|
+
|
26
|
+
if version_string.count >= index + 1 && Gem::Version.correct?(version_string[index])
|
27
|
+
return Gem::Version.new(version_string[index])
|
28
|
+
end
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/perfume.gif
ADDED
Binary file
|
@@ -0,0 +1,365 @@
|
|
1
|
+
//
|
2
|
+
// Blueprint.swift
|
3
|
+
// Representor
|
4
|
+
//
|
5
|
+
// Created by Kyle Fuller on 06/01/2015.
|
6
|
+
// Copyright (c) 2015 Apiary. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import Foundation
|
10
|
+
|
11
|
+
// MARK: Models
|
12
|
+
|
13
|
+
/// A structure representing an API Blueprint AST
|
14
|
+
public struct Blueprint {
|
15
|
+
/// Name of the API
|
16
|
+
public let name:String
|
17
|
+
|
18
|
+
/// Top-level description of the API in Markdown (.raw) or HTML (.html)
|
19
|
+
public let description:String?
|
20
|
+
|
21
|
+
/// The collection of resource groups
|
22
|
+
public let resourceGroups:[ResourceGroup]
|
23
|
+
|
24
|
+
public init(name:String, description:String?, resourceGroups:[ResourceGroup]) {
|
25
|
+
self.name = name
|
26
|
+
self.description = description
|
27
|
+
self.resourceGroups = resourceGroups
|
28
|
+
}
|
29
|
+
|
30
|
+
public init(ast:[String:AnyObject]) {
|
31
|
+
name = ast["name"] as String
|
32
|
+
description = ast["description"] as? String
|
33
|
+
resourceGroups = parseBlueprintResourceGroups(ast)
|
34
|
+
}
|
35
|
+
|
36
|
+
public init?(named:String, bundle:NSBundle? = nil) {
|
37
|
+
func loadFile(named:String, bundle:NSBundle) -> [String:AnyObject]? {
|
38
|
+
if let url = bundle.URLForResource(named, withExtension: nil) {
|
39
|
+
if let data = NSData(contentsOfURL: url) {
|
40
|
+
let object: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions(0), error: nil)
|
41
|
+
return object as? [String:AnyObject]
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
return nil
|
46
|
+
}
|
47
|
+
|
48
|
+
let ast = loadFile(named, bundle ?? NSBundle.mainBundle())
|
49
|
+
if let ast = ast {
|
50
|
+
self.init(ast: ast)
|
51
|
+
} else {
|
52
|
+
return nil
|
53
|
+
}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
/// Logical group of resources.
|
58
|
+
public struct ResourceGroup {
|
59
|
+
/// Name of the Resource Group
|
60
|
+
public let name:String
|
61
|
+
|
62
|
+
/// Description of the Resource Group (.raw or .html)
|
63
|
+
public let description:String?
|
64
|
+
|
65
|
+
/// Array of the respective resources belonging to the Resource Group
|
66
|
+
public let resources:[Resource]
|
67
|
+
|
68
|
+
public init(name:String, description:String?, resources:[Resource]) {
|
69
|
+
self.name = name
|
70
|
+
self.description = description
|
71
|
+
self.resources = resources
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
/// Description of one resource, or a cluster of resources defined by its URI template
|
76
|
+
public struct Resource {
|
77
|
+
/// Name of the Resource
|
78
|
+
public let name:String
|
79
|
+
|
80
|
+
/// Description of the Resource (.raw or .html)
|
81
|
+
public let description:String?
|
82
|
+
|
83
|
+
/// URI Template as defined in RFC6570
|
84
|
+
// TODO, make this a URITemplate object
|
85
|
+
public let uriTemplate:String
|
86
|
+
|
87
|
+
/// Array of URI parameters
|
88
|
+
public let parameters:[Parameter]
|
89
|
+
|
90
|
+
/// Array of actions available on the resource each defining at least one complete HTTP transaction
|
91
|
+
public let actions:[Action]
|
92
|
+
|
93
|
+
public init(name:String, description:String?, uriTemplate:String, parameters:[Parameter], actions:[Action]) {
|
94
|
+
self.name = name
|
95
|
+
self.description = description
|
96
|
+
self.uriTemplate = uriTemplate
|
97
|
+
self.actions = actions
|
98
|
+
self.parameters = parameters
|
99
|
+
}
|
100
|
+
}
|
101
|
+
|
102
|
+
/// Description of one URI template parameter
|
103
|
+
public struct Parameter {
|
104
|
+
/// Name of the parameter
|
105
|
+
public let name:String
|
106
|
+
|
107
|
+
/// Description of the Parameter (.raw or .html)
|
108
|
+
public let description:String?
|
109
|
+
|
110
|
+
/// An arbitrary type of the parameter (a string)
|
111
|
+
public let type:String?
|
112
|
+
|
113
|
+
/// Boolean flag denoting whether the parameter is required (true) or not (false)
|
114
|
+
public let required:Bool
|
115
|
+
|
116
|
+
/// A default value of the parameter (a value assumed when the parameter is not specified)
|
117
|
+
public let defaultValue:String?
|
118
|
+
|
119
|
+
/// An example value of the parameter
|
120
|
+
public let example:String?
|
121
|
+
|
122
|
+
/// An array enumerating possible parameter values
|
123
|
+
public let values:[String]?
|
124
|
+
|
125
|
+
public init(name:String, description:String?, type:String?, required:Bool, defaultValue:String?, example:String?, values:[String]?) {
|
126
|
+
self.name = name
|
127
|
+
self.description = description
|
128
|
+
self.type = type
|
129
|
+
self.required = required
|
130
|
+
self.defaultValue = defaultValue
|
131
|
+
self.example = example
|
132
|
+
self.values = values
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
// An HTTP transaction (a request-response transaction). Actions are specified by an HTTP request method within a resource
|
137
|
+
public struct Action {
|
138
|
+
/// Name of the Action
|
139
|
+
public let name:String
|
140
|
+
|
141
|
+
/// Description of the Action (.raw or .html)
|
142
|
+
public let description:String?
|
143
|
+
|
144
|
+
/// HTTP request method defining the action
|
145
|
+
public let method:String
|
146
|
+
|
147
|
+
/// Array of URI parameters
|
148
|
+
public let parameters:[Parameter]
|
149
|
+
|
150
|
+
/// URI Template for the action, if it differs from the resource's URI
|
151
|
+
public let uriTemplate:String?
|
152
|
+
|
153
|
+
/// Link relation identifier of the action
|
154
|
+
public let relation:String?
|
155
|
+
|
156
|
+
/// HTTP transaction examples for the relevant HTTP request method
|
157
|
+
public let examples:[TransactionExample]
|
158
|
+
|
159
|
+
public init(name:String, description:String?, method:String, parameters:[Parameter], uriTemplate:String? = nil, relation:String? = nil, examples:[TransactionExample]? = nil) {
|
160
|
+
self.name = name
|
161
|
+
self.description = description
|
162
|
+
self.method = method
|
163
|
+
self.parameters = parameters
|
164
|
+
self.uriTemplate = uriTemplate
|
165
|
+
self.relation = relation
|
166
|
+
self.examples = examples ?? []
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
/// An HTTP transaction example with expected HTTP message request and response payload
|
171
|
+
public struct TransactionExample {
|
172
|
+
/// Name of the Transaction Example
|
173
|
+
public let name:String
|
174
|
+
|
175
|
+
/// Description of the Transaction Example (.raw or .html)
|
176
|
+
public let description:String?
|
177
|
+
|
178
|
+
/// Example transaction request payloads
|
179
|
+
public let requests:[Payload]
|
180
|
+
|
181
|
+
/// Example transaction response payloads
|
182
|
+
public let responses:[Payload]
|
183
|
+
|
184
|
+
public init(name:String, description:String? = nil, requests:[Payload]? = nil, responses:[Payload]? = nil) {
|
185
|
+
self.name = name
|
186
|
+
self.description = description
|
187
|
+
self.requests = requests ?? []
|
188
|
+
self.responses = responses ?? []
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
|
193
|
+
/// An API Blueprint payload.
|
194
|
+
public struct Payload {
|
195
|
+
public typealias Header = (name:String, value:String)
|
196
|
+
|
197
|
+
/// Name of the payload
|
198
|
+
public let name:String
|
199
|
+
|
200
|
+
/// Description of the Payload (.raw or .html)
|
201
|
+
public let description:String?
|
202
|
+
|
203
|
+
/// HTTP headers that are expected to be transferred with HTTP message represented by this payload
|
204
|
+
public let headers:[Header]
|
205
|
+
|
206
|
+
/// An entity body to be transferred with HTTP message represented by this payload
|
207
|
+
public let body:NSData?
|
208
|
+
|
209
|
+
public init(name:String, description:String? = nil, headers:[Header]? = nil, body:NSData? = nil) {
|
210
|
+
self.name = name
|
211
|
+
self.description = description
|
212
|
+
self.headers = headers ?? []
|
213
|
+
self.body = body
|
214
|
+
}
|
215
|
+
}
|
216
|
+
|
217
|
+
|
218
|
+
// MARK: AST Parsing
|
219
|
+
|
220
|
+
func compactMap<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T?) -> [T] {
|
221
|
+
var collection = [T]()
|
222
|
+
|
223
|
+
for element in source {
|
224
|
+
if let item = transform(element) {
|
225
|
+
collection.append(item)
|
226
|
+
}
|
227
|
+
}
|
228
|
+
|
229
|
+
return collection
|
230
|
+
}
|
231
|
+
|
232
|
+
func parseParameter(source:[[String:AnyObject]]?) -> [Parameter] {
|
233
|
+
if let source = source {
|
234
|
+
return source.map { item in
|
235
|
+
let name = item["name"] as String
|
236
|
+
let description = item["description"] as? String
|
237
|
+
let type = item["type"] as? String
|
238
|
+
let required = item["required"] as? Bool
|
239
|
+
let defaultValue = item["default"] as? String
|
240
|
+
let example = item["example"] as? String
|
241
|
+
let values = item["values"] as? [String]
|
242
|
+
return Parameter(name: name, description: description, type: type, required: required ?? true, defaultValue: defaultValue, example: example, values: values)
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
return []
|
247
|
+
}
|
248
|
+
|
249
|
+
func parseActions(source:[[String:AnyObject]]?) -> [Action] {
|
250
|
+
if let source = source {
|
251
|
+
return compactMap(source) { item in
|
252
|
+
let name = item["name"] as? String
|
253
|
+
let description = item["description"] as? String
|
254
|
+
let method = item["method"] as? String
|
255
|
+
let parameters = parseParameter(item["parameters"] as? [[String:AnyObject]])
|
256
|
+
let attributes = item["attributes"] as? [String:String]
|
257
|
+
let uriTemplate = attributes?["uriTemplate"]
|
258
|
+
let relation = attributes?["relation"]
|
259
|
+
let examples = parseExamples(item["examples"] as? [[String:AnyObject]])
|
260
|
+
|
261
|
+
if let name = name {
|
262
|
+
if let method = method {
|
263
|
+
return Action(name: name, description: description, method: method, parameters: parameters, uriTemplate:uriTemplate, relation:relation, examples:examples)
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
return nil
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
return []
|
272
|
+
}
|
273
|
+
|
274
|
+
func parseExamples(source:[[String:AnyObject]]?) -> [TransactionExample] {
|
275
|
+
if let source = source {
|
276
|
+
return compactMap(source) { item in
|
277
|
+
let name = item["name"] as? String
|
278
|
+
let description = item["description"] as? String
|
279
|
+
let requests = parsePayloads(item["requests"] as? [[String:AnyObject]])
|
280
|
+
let responses = parsePayloads(item["responses"] as? [[String:AnyObject]])
|
281
|
+
|
282
|
+
if let name = name {
|
283
|
+
return TransactionExample(name: name, description: description, requests: requests, responses: responses)
|
284
|
+
}
|
285
|
+
|
286
|
+
return nil
|
287
|
+
}
|
288
|
+
}
|
289
|
+
|
290
|
+
return []
|
291
|
+
}
|
292
|
+
|
293
|
+
func parsePayloads(source:[[String:AnyObject]]?) -> [Payload] {
|
294
|
+
if let source = source {
|
295
|
+
return compactMap(source) { item in
|
296
|
+
let name = item["name"] as? String
|
297
|
+
let description = item["description"] as? String
|
298
|
+
let headers = parseHeaders(item["headers"] as? [[String:String]])
|
299
|
+
let bodyString = item["body"] as? String
|
300
|
+
let body = bodyString?.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
|
301
|
+
|
302
|
+
if let name = name {
|
303
|
+
return Payload(name: name, description: description, headers: headers, body: body)
|
304
|
+
}
|
305
|
+
|
306
|
+
return nil
|
307
|
+
}
|
308
|
+
}
|
309
|
+
|
310
|
+
return []
|
311
|
+
}
|
312
|
+
|
313
|
+
func parseHeaders(source:[[String:String]]?) -> [Payload.Header] {
|
314
|
+
if let source = source {
|
315
|
+
return compactMap(source) { item in
|
316
|
+
if let name = item["name"] {
|
317
|
+
if let value = item["value"] {
|
318
|
+
return (name, value)
|
319
|
+
}
|
320
|
+
}
|
321
|
+
|
322
|
+
return nil
|
323
|
+
}
|
324
|
+
}
|
325
|
+
|
326
|
+
return []
|
327
|
+
}
|
328
|
+
|
329
|
+
func parseResources(source:[[String:AnyObject]]?) -> [Resource] {
|
330
|
+
if let source = source {
|
331
|
+
return compactMap(source) { item in
|
332
|
+
let name = item["name"] as? String
|
333
|
+
let description = item["description"] as? String
|
334
|
+
let uriTemplate = item["uriTemplate"] as? String
|
335
|
+
let actions = parseActions(item["actions"] as? [[String:AnyObject]])
|
336
|
+
let parameters = parseParameter(item["parameters"] as? [[String:AnyObject]])
|
337
|
+
|
338
|
+
if let name = name {
|
339
|
+
if let uriTemplate = uriTemplate {
|
340
|
+
return Resource(name: name, description: description, uriTemplate: uriTemplate, parameters: parameters, actions: actions)
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
344
|
+
return nil
|
345
|
+
}
|
346
|
+
}
|
347
|
+
|
348
|
+
return []
|
349
|
+
}
|
350
|
+
|
351
|
+
private func parseBlueprintResourceGroups(blueprint:Dictionary<String, AnyObject>) -> [ResourceGroup] {
|
352
|
+
if let resourceGroups = blueprint["resourceGroups"] as? [[String:AnyObject]] {
|
353
|
+
return compactMap(resourceGroups) { dictionary in
|
354
|
+
if let name = dictionary["name"] as String? {
|
355
|
+
let resources = parseResources(dictionary["resources"] as? [[String:AnyObject]])
|
356
|
+
let description = dictionary["description"] as String?
|
357
|
+
return ResourceGroup(name: name, description: description, resources: resources)
|
358
|
+
}
|
359
|
+
|
360
|
+
return nil
|
361
|
+
}
|
362
|
+
}
|
363
|
+
|
364
|
+
return []
|
365
|
+
}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
import Foundation
|
2
|
+
|
3
|
+
/// The result of a task execution
|
4
|
+
public typealias ChoreResult = (result: Int32, stdout: String, stderr: String)
|
5
|
+
|
6
|
+
private func string_trim(string: NSString!) -> String {
|
7
|
+
return string.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()) ?? ""
|
8
|
+
}
|
9
|
+
|
10
|
+
private func chore_task(command: String, _ arguments: [String] = [String](), stdin: String = "") -> ChoreResult {
|
11
|
+
let task = NSTask()
|
12
|
+
|
13
|
+
task.launchPath = command
|
14
|
+
task.arguments = arguments
|
15
|
+
|
16
|
+
if !(task.launchPath as NSString).absolutePath {
|
17
|
+
task.launchPath = (chore_task("/usr/bin/which", [task.launchPath])).stdout
|
18
|
+
}
|
19
|
+
|
20
|
+
var isDirectory: ObjCBool = false
|
21
|
+
|
22
|
+
if !NSFileManager.defaultManager().fileExistsAtPath(task.launchPath, isDirectory: &isDirectory) {
|
23
|
+
return (255, "", String(format: "%@: launch path not accessible", task.launchPath))
|
24
|
+
}
|
25
|
+
|
26
|
+
if (isDirectory) {
|
27
|
+
return (255, "", String(format: "%@: launch path is a directory", task.launchPath))
|
28
|
+
}
|
29
|
+
|
30
|
+
if !NSFileManager.defaultManager().isExecutableFileAtPath(task.launchPath) {
|
31
|
+
return (255, "", String(format: "%@: launch path not executable", task.launchPath))
|
32
|
+
}
|
33
|
+
|
34
|
+
if count(stdin) > 0 {
|
35
|
+
let stdinPipe = NSPipe()
|
36
|
+
task.standardInput = stdinPipe
|
37
|
+
let stdinHandle = stdinPipe.fileHandleForWriting
|
38
|
+
|
39
|
+
if let data = stdin.dataUsingEncoding(NSUTF8StringEncoding) {
|
40
|
+
stdinHandle.writeData(data)
|
41
|
+
stdinHandle.closeFile()
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
let stderrPipe = NSPipe()
|
46
|
+
task.standardError = stderrPipe
|
47
|
+
let stderrHandle = stderrPipe.fileHandleForReading
|
48
|
+
|
49
|
+
let stdoutPipe = NSPipe()
|
50
|
+
task.standardOutput = stdoutPipe
|
51
|
+
let stdoutHandle = stdoutPipe.fileHandleForReading
|
52
|
+
|
53
|
+
task.launch()
|
54
|
+
task.waitUntilExit()
|
55
|
+
|
56
|
+
let stderr = string_trim(NSString(data: stderrHandle.readDataToEndOfFile(), encoding: NSUTF8StringEncoding)) ?? ""
|
57
|
+
let stdout = string_trim(NSString(data: stdoutHandle.readDataToEndOfFile(), encoding: NSUTF8StringEncoding)) ?? ""
|
58
|
+
|
59
|
+
return (task.terminationStatus, stdout, stderr)
|
60
|
+
}
|
61
|
+
|
62
|
+
prefix operator > {}
|
63
|
+
|
64
|
+
/**
|
65
|
+
Executes a command.
|
66
|
+
|
67
|
+
:param: command The command to execute.
|
68
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
69
|
+
*/
|
70
|
+
public prefix func > (command: String) -> ChoreResult {
|
71
|
+
return chore_task(command)
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
Executes a command with arguments.
|
76
|
+
|
77
|
+
:param: command The command to execute and its arguments.
|
78
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
79
|
+
*/
|
80
|
+
public prefix func > (command: [String]) -> ChoreResult {
|
81
|
+
switch command.count {
|
82
|
+
case 0:
|
83
|
+
return (0, "", "")
|
84
|
+
case 1:
|
85
|
+
return chore_task(command[0])
|
86
|
+
default:
|
87
|
+
break
|
88
|
+
}
|
89
|
+
|
90
|
+
return chore_task(command[0], Array(command[1..<command.count]))
|
91
|
+
}
|
92
|
+
|
93
|
+
infix operator | {}
|
94
|
+
|
95
|
+
/**
|
96
|
+
Executes a command with standard input from another command.
|
97
|
+
|
98
|
+
:param: left The result of a previous command.
|
99
|
+
:param: right The command to execute.
|
100
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
101
|
+
*/
|
102
|
+
public func | (left: ChoreResult, right: String) -> ChoreResult {
|
103
|
+
return left|[right]
|
104
|
+
}
|
105
|
+
|
106
|
+
/**
|
107
|
+
Executes a command with standard input from another command.
|
108
|
+
|
109
|
+
:param: left The result of a previous command.
|
110
|
+
:param: right The command to execute and its arguments.
|
111
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
112
|
+
*/
|
113
|
+
public func | (left: ChoreResult, right: [String]) -> ChoreResult {
|
114
|
+
if left.result != 0 {
|
115
|
+
return left
|
116
|
+
}
|
117
|
+
|
118
|
+
let arguments = right.count >= 2 ? Array(right[1..<right.count]) : [String]()
|
119
|
+
return chore_task(right[0], arguments, stdin: left.stdout)
|
120
|
+
}
|
121
|
+
|
122
|
+
/**
|
123
|
+
Executes a closure with input from a previous command.
|
124
|
+
|
125
|
+
:param: left The result of a previous command.
|
126
|
+
:param: right The closure to execute.
|
127
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
128
|
+
*/
|
129
|
+
public func | (left: ChoreResult, right: ((String) -> String)) -> ChoreResult {
|
130
|
+
if left.result != 0 {
|
131
|
+
return left
|
132
|
+
}
|
133
|
+
|
134
|
+
return (0, right(left.stdout), "")
|
135
|
+
}
|
136
|
+
|
137
|
+
/**
|
138
|
+
Executes a command with input from a closure.
|
139
|
+
|
140
|
+
:param: left The closure to execute.
|
141
|
+
:param: right The command to execute.
|
142
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
143
|
+
*/
|
144
|
+
public func | (left: (() -> String), right: String) -> ChoreResult {
|
145
|
+
return (0, left(), "")|right
|
146
|
+
}
|
147
|
+
|
148
|
+
/**
|
149
|
+
Executes a command with input from a closure.
|
150
|
+
|
151
|
+
:param: left The closure to execute.
|
152
|
+
:param: right The command to execute and its arguments.
|
153
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
154
|
+
*/
|
155
|
+
public func | (left: (() -> String), right: [String]) -> ChoreResult {
|
156
|
+
return (0, left(), "")|right
|
157
|
+
}
|
158
|
+
|
159
|
+
/**
|
160
|
+
Executes a command with input from a string.
|
161
|
+
|
162
|
+
:param: left The string to use a stdin.
|
163
|
+
:param: right The command to execute.
|
164
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
165
|
+
*/
|
166
|
+
public func | (left: String, right: String) -> ChoreResult {
|
167
|
+
return (0, left, "")|right
|
168
|
+
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
Executes a command with input from a string.
|
172
|
+
|
173
|
+
:param: left The string to use a stdin.
|
174
|
+
:param: right The command to execute and its arguments.
|
175
|
+
:returns: A tuple containing the exit code, stdout and stderr output.
|
176
|
+
*/
|
177
|
+
public func | (left: String, right: [String]) -> ChoreResult {
|
178
|
+
return (0, left, "")|right
|
179
|
+
}
|
@@ -0,0 +1,149 @@
|
|
1
|
+
//
|
2
|
+
// Stargate.swift
|
3
|
+
// Stargate
|
4
|
+
//
|
5
|
+
// Created by Boris Bügling on 28/04/15.
|
6
|
+
// Copyright (c) 2015 Contentful GmbH. All rights reserved.
|
7
|
+
//
|
8
|
+
|
9
|
+
import MultipeerConnectivity
|
10
|
+
import PeerKit
|
11
|
+
|
12
|
+
public typealias DebugHandler = (message: String) -> Void
|
13
|
+
|
14
|
+
public class Base {
|
15
|
+
private var applicationGroupIdentifier: String = ""
|
16
|
+
var pingIdentifier = "274EAEF1-A178-47FE-81F4-96E87C242456"
|
17
|
+
var pingPayload = "ping"
|
18
|
+
var sanitizedIdentifier: String {
|
19
|
+
return applicationGroupIdentifier.stringByReplacingOccurrencesOfString(".", withString: "", options: NSStringCompareOptions.allZeros, range: nil).substringToIndex(advance(applicationGroupIdentifier.startIndex, 15))
|
20
|
+
}
|
21
|
+
|
22
|
+
public init(applicationGroupIdentifier: String) {
|
23
|
+
self.applicationGroupIdentifier = applicationGroupIdentifier
|
24
|
+
}
|
25
|
+
|
26
|
+
public func listenForMessage(#identifier: String, _ listener: ((AnyObject!) -> Void)) {
|
27
|
+
fatalError("listenForMessage() needs to be overidden in subclasses.")
|
28
|
+
}
|
29
|
+
|
30
|
+
public func passMessage(message: NSCoding, identifier: String) {
|
31
|
+
fatalError("passMessageObject() needs to be overidden in subclasses.")
|
32
|
+
}
|
33
|
+
|
34
|
+
public func sendMultipeerMessage(message: AnyObject, identifier: String) {
|
35
|
+
let allPeers = PeerKit.session?.connectedPeers as? [MCPeerID]
|
36
|
+
PeerKit.sendEvent(identifier, object: message, toPeers: allPeers)
|
37
|
+
}
|
38
|
+
|
39
|
+
public func stopListeningForMessage(#identifier: String) {
|
40
|
+
fatalError("stopListeningForMessage() needs to be overidden in subclasses.")
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
#if os(iOS)
|
45
|
+
import MMWormhole
|
46
|
+
import WatchKit
|
47
|
+
import UIKit
|
48
|
+
|
49
|
+
/// Stargate endpoint to be used on the phone
|
50
|
+
public class Abydos : Base {
|
51
|
+
private var callback: DebugHandler?
|
52
|
+
var wormhole: MMWormhole!
|
53
|
+
|
54
|
+
public override init(applicationGroupIdentifier: String) {
|
55
|
+
super.init(applicationGroupIdentifier: applicationGroupIdentifier)
|
56
|
+
|
57
|
+
PeerKit.transceive(sanitizedIdentifier)
|
58
|
+
sendMultipeerMessage(pingPayload, identifier: pingIdentifier)
|
59
|
+
|
60
|
+
wormhole = MMWormhole(applicationGroupIdentifier: applicationGroupIdentifier, optionalDirectory: "stargate")
|
61
|
+
wormhole.passMessageObject(pingPayload, identifier: pingIdentifier)
|
62
|
+
}
|
63
|
+
|
64
|
+
public func debug(callback: DebugHandler) {
|
65
|
+
PeerKit.onConnect = { (me, you) -> Void in callback(message: "connect: \(me) <=> \(you)") }
|
66
|
+
self.callback = callback
|
67
|
+
}
|
68
|
+
|
69
|
+
public func tunnel() {
|
70
|
+
PeerKit.transceive(sanitizedIdentifier)
|
71
|
+
PeerKit.onEvent = { (peerID, event, object) -> Void in
|
72
|
+
if let object = object as? NSCoding {
|
73
|
+
if let callback = self.callback {
|
74
|
+
callback(message: "Received message from Mac: \(object) for \(event)")
|
75
|
+
}
|
76
|
+
|
77
|
+
self.wormhole.passMessageObject(object, identifier: event)
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
//UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler() {}
|
82
|
+
}
|
83
|
+
|
84
|
+
public func tunnelReplies(#identifier: String) {
|
85
|
+
wormhole.listenForMessageWithIdentifier(identifier) { (message) -> Void in
|
86
|
+
if let message: AnyObject = message {
|
87
|
+
if let callback = self.callback {
|
88
|
+
callback(message: "Received message from watch: \(message) for \(identifier)")
|
89
|
+
}
|
90
|
+
|
91
|
+
self.sendMultipeerMessage(message, identifier: identifier)
|
92
|
+
}
|
93
|
+
}
|
94
|
+
}
|
95
|
+
}
|
96
|
+
|
97
|
+
/// Stargate endpoint to be used on the ᴡᴀᴛᴄʜ
|
98
|
+
public class Atlantis : Base {
|
99
|
+
var wormhole: MMWormhole!
|
100
|
+
|
101
|
+
public override init(applicationGroupIdentifier: String) {
|
102
|
+
super.init(applicationGroupIdentifier: applicationGroupIdentifier)
|
103
|
+
|
104
|
+
wormhole = MMWormhole(applicationGroupIdentifier: applicationGroupIdentifier, optionalDirectory: "stargate")
|
105
|
+
passMessage(pingPayload, identifier: pingIdentifier)
|
106
|
+
}
|
107
|
+
|
108
|
+
public override func listenForMessage(#identifier: String, _ listener: ((AnyObject!) -> Void)) {
|
109
|
+
wormhole.listenForMessageWithIdentifier(identifier, listener: listener)
|
110
|
+
|
111
|
+
WKInterfaceController.openParentApplication([NSObject : AnyObject](), reply: nil)
|
112
|
+
}
|
113
|
+
|
114
|
+
public override func passMessage(message: NSCoding, identifier: String) {
|
115
|
+
wormhole.passMessageObject(message, identifier: identifier)
|
116
|
+
|
117
|
+
WKInterfaceController.openParentApplication([NSObject : AnyObject](), reply: nil)
|
118
|
+
}
|
119
|
+
|
120
|
+
public override func stopListeningForMessage(#identifier: String) {
|
121
|
+
wormhole.stopListeningForMessageWithIdentifier(identifier)
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
#endif
|
126
|
+
|
127
|
+
/// Stargate endpoint to be used on the Mac
|
128
|
+
public class Earth : Base {
|
129
|
+
public override init(applicationGroupIdentifier: String) {
|
130
|
+
super.init(applicationGroupIdentifier: applicationGroupIdentifier)
|
131
|
+
|
132
|
+
PeerKit.transceive(sanitizedIdentifier)
|
133
|
+
passMessage(pingPayload, identifier: pingIdentifier)
|
134
|
+
}
|
135
|
+
|
136
|
+
public override func listenForMessage(#identifier: String, _ listener: ((AnyObject!) -> Void)) {
|
137
|
+
PeerKit.eventBlocks[identifier] = { (peerID, object) -> Void in
|
138
|
+
listener(object)
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
public override func passMessage(message: NSCoding, identifier: String) {
|
143
|
+
sendMultipeerMessage(message, identifier: identifier)
|
144
|
+
}
|
145
|
+
|
146
|
+
public override func stopListeningForMessage(#identifier: String) {
|
147
|
+
PeerKit.stopTransceiving()
|
148
|
+
}
|
149
|
+
}
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require 'pathname'
|
5
|
+
ROOT = Pathname.new(File.expand_path('../../', __FILE__))
|
6
|
+
$LOAD_PATH.unshift((ROOT + 'lib').to_s)
|
7
|
+
$LOAD_PATH.unshift((ROOT + 'spec').to_s)
|
8
|
+
|
9
|
+
require 'bundler/setup'
|
10
|
+
require 'bacon'
|
11
|
+
require 'mocha-on-bacon'
|
12
|
+
require 'pretty_bacon'
|
13
|
+
|
14
|
+
require 'grenouille'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
module Grenouille
|
4
|
+
describe SwiftUpdate do
|
5
|
+
it 'reports Swift 1.2 code correctly' do
|
6
|
+
detected_version = SwiftUpdate.new.determine_version('spec/fixtures/Stargate.swift')
|
7
|
+
|
8
|
+
detected_version.should == Gem::Version.new('1.2')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'reports Swift 1.1 code correctly' do
|
12
|
+
detected_version = SwiftUpdate.new.determine_version('spec/fixtures/Blueprint.swift')
|
13
|
+
|
14
|
+
detected_version.should == Gem::Version.new('1.1')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can handle Swift 1.2 on iOS' do
|
18
|
+
result = SwiftUpdate.new.update_to_latest_swift('spec/fixtures/Stargate.swift')
|
19
|
+
|
20
|
+
result[:report].should == []
|
21
|
+
result[:output].start_with?('spec/fixtures/Stargate.swift:10:8: error: no such module \'PeerKit\'').should == true
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can handle Swift 1.1 on iOS' do
|
25
|
+
result = SwiftUpdate.new.update_to_latest_swift('spec/fixtures/Blueprint.swift')
|
26
|
+
|
27
|
+
result[:report].count.should == 3
|
28
|
+
result[:output].should == ''
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'can handle Swift 1.2 on OS X' do
|
32
|
+
result = SwiftUpdate.new.update_to_latest_swift('spec/fixtures/ChoreTask.swift')
|
33
|
+
|
34
|
+
result[:report].should == []
|
35
|
+
result[:output].should == ''
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'can handle Swift 1.1 on OS X' do
|
39
|
+
result = SwiftUpdate.new.update_to_latest_swift('spec/fixtures/Mac-1.1.swift')
|
40
|
+
|
41
|
+
result[:report].count.should == 1
|
42
|
+
result[:output].should == ''
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'supports file globbing' do
|
46
|
+
detected_version = SwiftUpdate.new.determine_version('spec/fixtures/*.swift')
|
47
|
+
|
48
|
+
detected_version.should == Gem::Version.new('1.1')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'accepts a list of files' do
|
52
|
+
detected_version = SwiftUpdate.new.determine_version(['spec/fixtures/ChoresTask.swift', 'spec/fixtures/Mac-1.1.swift'])
|
53
|
+
|
54
|
+
detected_version.should == Gem::Version.new('1.1')
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'throws when non Swift files are provided as arguments' do
|
58
|
+
should.raise(StandardError) { SwiftUpdate.new.determine_version('spec/*.rb') }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/spec/xcode_spec.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
module Grenouille
|
4
|
+
describe Xcode do
|
5
|
+
it 'can retrieve the version number of the currently selected Swift' do
|
6
|
+
Xcode.any_instance.stubs(:`).returns("Apple Swift version 1.3 (swiftlang-602.0.49.6 clang-602.0.49)\nTarget: x86_64-apple-darwin14.4.0")
|
7
|
+
|
8
|
+
Xcode.new.current_swift_version.should == Gem::Version.new('1.3')
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'can retrieve the version of Swift 1.1' do
|
12
|
+
Xcode.any_instance.stubs(:`).returns('Swift version 1.1 (swift-600.0.57.4)')
|
13
|
+
|
14
|
+
Xcode.new.current_swift_version.should == Gem::Version.new('1.1')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'can retrieve the version number of the currently selected Xcode' do
|
18
|
+
Xcode.any_instance.stubs(:`).returns("Xcode 3.2.1\nBuild version 6D1002")
|
19
|
+
|
20
|
+
Xcode.new.current_version.should == Gem::Version.new('3.2.1')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'is resilient against xcodebuild not producing any output' do
|
24
|
+
Xcode.any_instance.stubs(:`).returns('')
|
25
|
+
|
26
|
+
Xcode.new.current_version.should.be.nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'is resilient against xcodebuild not producing the desired output' do
|
30
|
+
Xcode.any_instance.stubs(:`).returns(File.read(__FILE__))
|
31
|
+
|
32
|
+
Xcode.new.current_version.should.be.nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: grenouille
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Boris Bügling
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-05-06 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
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
|
+
description:
|
42
|
+
email:
|
43
|
+
- boris@buegling.com
|
44
|
+
executables:
|
45
|
+
- grenouille
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- .gitignore
|
50
|
+
- .rubocop.yml
|
51
|
+
- .swift-version
|
52
|
+
- Gemfile
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- bin/grenouille
|
57
|
+
- circle.yml
|
58
|
+
- grenouille.gemspec
|
59
|
+
- lib/grenouille.rb
|
60
|
+
- lib/grenouille/swift_update.rb
|
61
|
+
- lib/grenouille/version.rb
|
62
|
+
- lib/grenouille/xcode.rb
|
63
|
+
- perfume.gif
|
64
|
+
- spec/fixtures/Blueprint.swift
|
65
|
+
- spec/fixtures/ChoreTask.swift
|
66
|
+
- spec/fixtures/Mac-1.1.swift
|
67
|
+
- spec/fixtures/Stargate.swift
|
68
|
+
- spec/spec_helper.rb
|
69
|
+
- spec/swift_update_spec.rb
|
70
|
+
- spec/xcode_spec.rb
|
71
|
+
homepage: https://github.com/neonichu/grenouille
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata: {}
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 2.0.14
|
92
|
+
signing_key:
|
93
|
+
specification_version: 4
|
94
|
+
summary: Automatically detect Swift versions from source code.
|
95
|
+
test_files:
|
96
|
+
- spec/fixtures/Blueprint.swift
|
97
|
+
- spec/fixtures/ChoreTask.swift
|
98
|
+
- spec/fixtures/Mac-1.1.swift
|
99
|
+
- spec/fixtures/Stargate.swift
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
- spec/swift_update_spec.rb
|
102
|
+
- spec/xcode_spec.rb
|
103
|
+
has_rdoc:
|