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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rubocop.yml ADDED
@@ -0,0 +1,14 @@
1
+ Lint/Void:
2
+ Enabled: false
3
+
4
+ Metrics/AbcSize:
5
+ Max: 30
6
+
7
+ Metrics/LineLength:
8
+ Max: 142
9
+
10
+ Metrics/MethodLength:
11
+ Max: 18
12
+
13
+ Style/Documentation:
14
+ Enabled: false
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
+ [![Build Status](https://img.shields.io/circleci/project/neonichu/grenouille.svg?style=flat)](https://circleci.com/gh/neonichu/grenouille)
4
+ [![Coverage Status](https://coveralls.io/repos/neonichu/grenouille/badge.svg)](https://coveralls.io/r/neonichu/grenouille)
5
+ [![Gem Version](http://img.shields.io/gem/v/grenouille.svg?style=flat)](http://badge.fury.io/rb/grenouille)
6
+ [![Code Climate](http://img.shields.io/codeclimate/github/neonichu/grenouille.svg?style=flat)](https://codeclimate.com/github/neonichu/grenouille)
7
+
8
+ ![](perfume.gif)
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
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ def specs(dir)
4
+ FileList["spec/#{dir}/*_spec.rb"].shuffle.join(' ')
5
+ end
6
+
7
+ desc 'Runs all the specs'
8
+ task :spec do
9
+ sh "bundle exec bacon #{specs('**')}"
10
+ end
11
+
12
+ task default: :specs
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
@@ -0,0 +1,10 @@
1
+ machine:
2
+ environment:
3
+ LANG: en_US.UTF-8
4
+ XCODE_PROJECT: yolo.xcodeproj
5
+ XCODE_SCHEME: shrugs
6
+ xcode:
7
+ version: "6.3.1"
8
+ test:
9
+ override:
10
+ - rake spec
@@ -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,3 @@
1
+ require 'grenouille/swift_update'
2
+ require 'grenouille/version'
3
+ require 'grenouille/xcode'
@@ -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,3 @@
1
+ module Grenouille
2
+ VERSION = '0.0.1'
3
+ 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,8 @@
1
+ import Foundation
2
+ import AppKit
3
+
4
+ private func chore_task() {
5
+ let task = NSTask()
6
+ let array = [1,2,3]
7
+ println(countElements(array))
8
+ }
@@ -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
+ }
@@ -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
@@ -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: