stronglyboards 0.0.2
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 +83 -0
- data/LICENSE.md +21 -0
- data/README.md +107 -0
- data/bin/stronglyboards +3 -0
- data/lib/stronglyboards.rb +253 -0
- data/lib/stronglyboards/lock_file.rb +34 -0
- data/lib/stronglyboards/source_generator.rb +43 -0
- data/lib/stronglyboards/source_generator_objc.rb +175 -0
- data/lib/stronglyboards/source_generator_swift.rb +107 -0
- data/lib/stronglyboards/storyboard.rb +69 -0
- data/lib/stronglyboards/version.rb +3 -0
- data/lib/stronglyboards/view_controller.rb +47 -0
- data/stronglyboards.gemspec +30 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 74195599601bd31170e951aaf41ecbf811a32694
|
4
|
+
data.tar.gz: 495710653267bd90a9d856e72ce367281d538cea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 287f024d27278f24ec0263189ef00e62fbb76eb6b4a29dc649ed9cae589c61cd7af71b4ad87667e2f902eb8304bedd3d2197aa92732017cf7ade87abcaa2019e
|
7
|
+
data.tar.gz: 1a39d4402e55a4915b83b92fed4ceaf6f8724dfba923a36abda590caed519a405f99ab26afbdee6b6542ec7e8858c30e89a091a5b578eb907d6977bd0995c4e2
|
data/.gitignore
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
## Specific to RubyMotion:
|
14
|
+
.dat*
|
15
|
+
.repl_history
|
16
|
+
build/
|
17
|
+
|
18
|
+
## Documentation cache and generated files:
|
19
|
+
/.yardoc/
|
20
|
+
/_yardoc/
|
21
|
+
/doc/
|
22
|
+
/rdoc/
|
23
|
+
|
24
|
+
## Environment normalisation:
|
25
|
+
/.bundle/
|
26
|
+
/vendor/bundle
|
27
|
+
/lib/bundler/man/
|
28
|
+
|
29
|
+
# for a library or gem, you might want to ignore these files since the code is
|
30
|
+
# intended to run in multiple environments; otherwise, check them in:
|
31
|
+
# Gemfile.lock
|
32
|
+
# .ruby-version
|
33
|
+
# .ruby-gemset
|
34
|
+
|
35
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
36
|
+
.rvmrc
|
37
|
+
|
38
|
+
# Below covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio
|
39
|
+
|
40
|
+
*.iml
|
41
|
+
|
42
|
+
## Directory-based project format:
|
43
|
+
.idea/
|
44
|
+
# if you remove the above rule, at least ignore the following:
|
45
|
+
|
46
|
+
# User-specific stuff:
|
47
|
+
# .idea/workspace.xml
|
48
|
+
# .idea/tasks.xml
|
49
|
+
# .idea/dictionaries
|
50
|
+
|
51
|
+
# Sensitive or high-churn files:
|
52
|
+
# .idea/dataSources.ids
|
53
|
+
# .idea/dataSources.xml
|
54
|
+
# .idea/sqlDataSources.xml
|
55
|
+
# .idea/dynamic.xml
|
56
|
+
# .idea/uiDesigner.xml
|
57
|
+
|
58
|
+
# Gradle:
|
59
|
+
# .idea/gradle.xml
|
60
|
+
# .idea/libraries
|
61
|
+
|
62
|
+
# Mongo Explorer plugin:
|
63
|
+
# .idea/mongoSettings.xml
|
64
|
+
|
65
|
+
## File-based project format:
|
66
|
+
*.ipr
|
67
|
+
*.iws
|
68
|
+
|
69
|
+
## Plugin-specific files:
|
70
|
+
|
71
|
+
# IntelliJ
|
72
|
+
/out/
|
73
|
+
|
74
|
+
# mpeltonen/sbt-idea plugin
|
75
|
+
.idea_modules/
|
76
|
+
|
77
|
+
# JIRA plugin
|
78
|
+
atlassian-ide-plugin.xml
|
79
|
+
|
80
|
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
81
|
+
com_crashlytics_export_strings.xml
|
82
|
+
crashlytics.properties
|
83
|
+
crashlytics-build.properties
|
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Steve Wilford
|
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 all
|
13
|
+
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 THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Overview #
|
2
|
+
|
3
|
+
Stronglyboards generates code that can be used to provide a strongly-typed interface to [iOS Storyboards](https://developer.apple.com/library/ios/recipes/xcode_help-IB_storyboard/chapters/AboutStoryboards.html).
|
4
|
+
|
5
|
+
Reduce mistakes introduced when using string based storyboard and view controller identifiers.
|
6
|
+
|
7
|
+
It is inspired by [Natalie](https://github.com/krzyzanowskim/Natalie) but is a [Ruby gem](https://rubygems.org) and generates a slightly different API,
|
8
|
+
and I believe [competition is a good thing](https://vimeo.com/124317403).
|
9
|
+
|
10
|
+
# Features
|
11
|
+
|
12
|
+
- Safe instantiation of:-
|
13
|
+
- storyboards,
|
14
|
+
- the storyboard's initial view controller,
|
15
|
+
- view controllers with storyboard identifiers
|
16
|
+
- Outputs **Objective-C** or **Swift** depending on your preference.
|
17
|
+
- Integrates seamlessly into your project.
|
18
|
+
- Creates a build phase to automatically keep the generated code up-to-date with storyboard changes.
|
19
|
+
- Supports **localized** and non-localized Storyboards.
|
20
|
+
|
21
|
+
# Todo #
|
22
|
+
|
23
|
+
- Segues
|
24
|
+
- Table and Collection View cells
|
25
|
+
- More...
|
26
|
+
|
27
|
+
# Installation & Basic Usage #
|
28
|
+
|
29
|
+
Use the `gem` command to install Stronglyboards:
|
30
|
+
|
31
|
+
```gem install stronglyboards```
|
32
|
+
|
33
|
+
Run stronglyboards on your Xcode project file:
|
34
|
+
|
35
|
+
```stronglyboards install MyProject.xcodeproj```
|
36
|
+
|
37
|
+
By default it will generate Objective-C files in the current directory.
|
38
|
+
|
39
|
+
Stronglyboards will automatically add the generated files into your project
|
40
|
+
and setup a new "Run Script" build phase to keep up-to-date with any
|
41
|
+
storyboard changes you might make.
|
42
|
+
|
43
|
+
# Usage #
|
44
|
+
|
45
|
+
`install <PROJECT>`
|
46
|
+
|
47
|
+
This will install Stronglyboards into the specified Xcode project. Replace `<PROJECT>` with your `.xcodeproj` file.
|
48
|
+
|
49
|
+
`update <PROJECT>`
|
50
|
+
|
51
|
+
This will attempt to update Stronglyboards in a project where it is already installed. Replace `<PROJECT>` with your `.xcodeproj` file.
|
52
|
+
Note that things will likely go wrong if you have manually renamed any of the generated files.
|
53
|
+
|
54
|
+
`uninstall <PROJECT>`
|
55
|
+
|
56
|
+
This will attempt to remove Stronglyboards from a project where it has been previously installed. Replace `<PROJECT>` with your `.xcodeproj` file.
|
57
|
+
Note that things will likely go wrong if you have manually renamed any of the generated files.
|
58
|
+
|
59
|
+
# Installation Options #
|
60
|
+
|
61
|
+
`--output` specifies the name of the output file(s).
|
62
|
+
You can use this parameter to output to a different directory.
|
63
|
+
e.g. `Classes/GeneratedStoryboardAPI`.
|
64
|
+
You should **not** provide a file extension as part of this file name.
|
65
|
+
This is an optional parameter, default is `Stronglyboards`.
|
66
|
+
Note that this path is essentially a template, the actual generated filenames
|
67
|
+
will be different.
|
68
|
+
|
69
|
+
`--language` specifies the output language as either `objc` or `swift`.
|
70
|
+
This is an optional parameter, default is `objc`.
|
71
|
+
|
72
|
+
`--prefix` specifies a string to be used as the prefix for all generated classes.
|
73
|
+
This is an optional parameter, default is no prefix.
|
74
|
+
Note that the prefix does not affect the output file name.
|
75
|
+
|
76
|
+
# Contributing #
|
77
|
+
|
78
|
+
Submit an issue, or ideally a pull request.
|
79
|
+
|
80
|
+
# Authors & Contributors #
|
81
|
+
|
82
|
+
- [@nxsteveo](http://twitter.com/nxsteveo)
|
83
|
+
- \<Your name here>
|
84
|
+
|
85
|
+
# License #
|
86
|
+
|
87
|
+
The MIT License (MIT)
|
88
|
+
|
89
|
+
Copyright (c) 2015 Steve Wilford
|
90
|
+
|
91
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
92
|
+
of this software and associated documentation files (the "Software"), to deal
|
93
|
+
in the Software without restriction, including without limitation the rights
|
94
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
95
|
+
copies of the Software, and to permit persons to whom the Software is
|
96
|
+
furnished to do so, subject to the following conditions:
|
97
|
+
|
98
|
+
The above copyright notice and this permission notice shall be included in all
|
99
|
+
copies or substantial portions of the Software.
|
100
|
+
|
101
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
102
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
103
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
104
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
105
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
106
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
107
|
+
SOFTWARE.
|
data/bin/stronglyboards
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'xcodeproj'
|
2
|
+
require 'optparse'
|
3
|
+
require 'thor'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require_relative 'stronglyboards/version'
|
7
|
+
require_relative 'stronglyboards/lock_file'
|
8
|
+
require_relative 'stronglyboards/storyboard'
|
9
|
+
require_relative 'stronglyboards/source_generator_objc'
|
10
|
+
require_relative 'stronglyboards/source_generator_swift'
|
11
|
+
|
12
|
+
module Stronglyboards
|
13
|
+
|
14
|
+
class Stronglyboards < Thor
|
15
|
+
|
16
|
+
BUILD_SCRIPT_NAME = 'Update Stronglyboards'
|
17
|
+
|
18
|
+
# ---- Begin external interface ----
|
19
|
+
|
20
|
+
desc 'install PROJECT', 'Installs Stronglyboards into your .xcodeproj file'
|
21
|
+
option :output, :desc => 'Path to the output file'
|
22
|
+
option :language, :default => 'objc', :desc => 'Output language (objc [default], swift)'
|
23
|
+
option :prefix, :default => '', :desc => 'Class and category method prefix'
|
24
|
+
def install(project_file)
|
25
|
+
lock_file = LockFile.new(project_file)
|
26
|
+
if lock_file.exists?
|
27
|
+
puts 'It appears that Stronglyboards has already been installed on this project.'
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
puts "Installing Stronglyboards into #{project_file}"
|
32
|
+
|
33
|
+
# Open the existing Xcode project
|
34
|
+
project = Xcodeproj::Project.open(project_file)
|
35
|
+
|
36
|
+
# Do main processing
|
37
|
+
process(project, options)
|
38
|
+
|
39
|
+
# Finalise installation
|
40
|
+
lock_file.update(options)
|
41
|
+
project.save
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'update', 'Updates the generated source code for the project'
|
45
|
+
def update(project_file)
|
46
|
+
configuration = require_lock_file(project_file).contents
|
47
|
+
|
48
|
+
puts "Updating Stronglyboards in #{project_file}"
|
49
|
+
|
50
|
+
# Open the Xcode project
|
51
|
+
project = Xcodeproj::Project.open(project_file)
|
52
|
+
|
53
|
+
process(project, configuration)
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'uninstall PROJECT', 'Uninstalls Stronglyboards from the specified .xcodeproj file.'
|
57
|
+
def uninstall(project_file)
|
58
|
+
lock_file = require_lock_file(project_file)
|
59
|
+
configuration = lock_file.contents
|
60
|
+
|
61
|
+
base_output_file = configuration[:output]
|
62
|
+
language = configuration[:language]
|
63
|
+
prefix = configuration[:prefix]
|
64
|
+
|
65
|
+
puts "Uninstalling Stronglyboards from #{project_file}"
|
66
|
+
|
67
|
+
files_to_delete = Array.new
|
68
|
+
|
69
|
+
# Open the Xcode project
|
70
|
+
project = Xcodeproj::Project.open(project_file)
|
71
|
+
project_root = project.path.dirname
|
72
|
+
|
73
|
+
# Gather the targets that we're interested in
|
74
|
+
targets = interesting_targets(project)
|
75
|
+
|
76
|
+
targets.each do |target|
|
77
|
+
# Find and delete the build script phase for this target
|
78
|
+
target.build_phases.select { |phase|
|
79
|
+
phase.is_a? Xcodeproj::Project::PBXShellScriptBuildPhase and phase.name == BUILD_SCRIPT_NAME
|
80
|
+
}.each { |phase|
|
81
|
+
target.build_phases.delete(phase)
|
82
|
+
}
|
83
|
+
|
84
|
+
# Get a source generator for this target
|
85
|
+
output_file = base_output_file_for_target(base_output_file, target, prefix)
|
86
|
+
source_generator = source_generator(language, prefix, output_file)
|
87
|
+
|
88
|
+
# Gather the files that would have been generated for this target.
|
89
|
+
# Bare in mind that this target may have been created since the
|
90
|
+
# last time the install or update command ran, so there may be
|
91
|
+
# files in this list that don't actually exist.
|
92
|
+
files_to_delete << source_generator.output_files
|
93
|
+
end
|
94
|
+
|
95
|
+
# Expand the paths of the files to be deleted to be absolute
|
96
|
+
files_to_delete.flatten!.uniq!
|
97
|
+
paths_to_delete = files_to_delete.collect do |file|
|
98
|
+
project_root + file.file.path
|
99
|
+
end
|
100
|
+
|
101
|
+
# Look through each target to see if this file is a member,
|
102
|
+
# removing it from the source build phase if necessary.
|
103
|
+
# TODO: There's got to be a better way, question asked.
|
104
|
+
# http://stackoverflow.com/questions/32908231/how-to-get-the-targets-for-a-pbxfilereference-in-xcodeproj
|
105
|
+
targets.each do |target|
|
106
|
+
target.source_build_phase.files.each do |build_file|
|
107
|
+
full_path = build_file.file_ref.real_path
|
108
|
+
if paths_to_delete.include?(full_path)
|
109
|
+
|
110
|
+
# Need to get a reference to the underlying file reference
|
111
|
+
# as it will be removed when the build file is removed
|
112
|
+
# from the target's build phase.
|
113
|
+
file = build_file.file_ref
|
114
|
+
|
115
|
+
# Remove the file from the build phase, project, and file system
|
116
|
+
target.source_build_phase.remove_build_file(build_file)
|
117
|
+
file.remove_from_project
|
118
|
+
File.delete(full_path)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Iterate through the files to delete to get rid of any non-source files
|
124
|
+
files_to_delete.each do |file|
|
125
|
+
unless file.is_source
|
126
|
+
full_path = File.realpath(file.file)
|
127
|
+
file_ref = project.reference_for_path(full_path)
|
128
|
+
file_ref.remove_from_project
|
129
|
+
File.delete(full_path)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
project.save
|
134
|
+
|
135
|
+
# Finally delete the lock file
|
136
|
+
lock_file.delete
|
137
|
+
end
|
138
|
+
|
139
|
+
# ---- End external interface ----
|
140
|
+
|
141
|
+
private
|
142
|
+
def process(project, options)
|
143
|
+
base_output_file = options[:output]
|
144
|
+
language = options[:language]
|
145
|
+
prefix = options[:prefix]
|
146
|
+
|
147
|
+
interesting_targets(project).each do |target|
|
148
|
+
|
149
|
+
# Provide a default output filename
|
150
|
+
output_file = base_output_file_for_target(base_output_file, target, prefix)
|
151
|
+
|
152
|
+
# Instantiate a source generator appropriate for the selected language
|
153
|
+
source_generator = source_generator(language, prefix, output_file)
|
154
|
+
|
155
|
+
# Iterate the target's resource files looking for storyboards
|
156
|
+
target.resources_build_phase.files.each do |build_file|
|
157
|
+
next unless build_file.display_name.end_with? Storyboard::EXTENSION
|
158
|
+
|
159
|
+
file_or_group = build_file.file_ref
|
160
|
+
|
161
|
+
if file_or_group.is_a? Xcodeproj::Project::Object::PBXFileReference
|
162
|
+
# Getting the real path is sufficient for non-localized storyboards
|
163
|
+
# as it will return the absolute path to the .storyboard
|
164
|
+
path = file_or_group.real_path
|
165
|
+
elsif file_or_group.is_a? Xcodeproj::Project::Object::PBXVariantGroup
|
166
|
+
# Localized storyboards will be a group and will
|
167
|
+
# need the path constructing from the Base storyboard.
|
168
|
+
base_storyboard_file = file_or_group.children.find { |f| f.name == 'Base' }
|
169
|
+
if base_storyboard_file == nil
|
170
|
+
puts "No Base storyboard found for #{file_or_group}!!!"
|
171
|
+
next
|
172
|
+
end
|
173
|
+
path = base_storyboard_file.real_path
|
174
|
+
end
|
175
|
+
|
176
|
+
storyboard = Storyboard.new(path)
|
177
|
+
|
178
|
+
source_generator.add_storyboard(storyboard)
|
179
|
+
end # end project file iterator
|
180
|
+
|
181
|
+
output_files = source_generator.parse_storyboards
|
182
|
+
|
183
|
+
# Add the output files to the target
|
184
|
+
add_files_to_target(project, target, output_files)
|
185
|
+
add_build_script(project, target)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
def interesting_targets(project)
|
191
|
+
project.native_targets.select { |target| target.product_type == 'com.apple.product-type.application' }
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
def base_output_file_for_target(base_file, target, prefix)
|
196
|
+
if base_file == nil
|
197
|
+
base_file = prefix + 'Stronglyboards'
|
198
|
+
end
|
199
|
+
base_file + "_#{target.name}"
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
def add_files_to_target(project, target, output_files)
|
204
|
+
puts "Adding files to target \"#{target}\""
|
205
|
+
|
206
|
+
output_files.each do |output_file|
|
207
|
+
# Insert the file into the root group in the project
|
208
|
+
file_reference = project.new_file(output_file.file)
|
209
|
+
|
210
|
+
# Add the file to the target to ensure it is compiled
|
211
|
+
target.source_build_phase.add_file_reference(file_reference) if output_file.is_source
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
private
|
217
|
+
def add_build_script(project, target)
|
218
|
+
puts 'Adding build script'
|
219
|
+
|
220
|
+
phase = project.new(Xcodeproj::Project::Object::PBXShellScriptBuildPhase)
|
221
|
+
phase.name = BUILD_SCRIPT_NAME
|
222
|
+
phase.shell_script = 'stronglyboards update ${PROJECT_NAME}'
|
223
|
+
target.build_phases.insert(0, phase)
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
def source_generator(language, prefix, output_file)
|
228
|
+
case language
|
229
|
+
when 'objc'
|
230
|
+
SourceGeneratorObjC.new(prefix, output_file)
|
231
|
+
when 'swift'
|
232
|
+
SourceGeneratorSwift.new(prefix, output_file)
|
233
|
+
else
|
234
|
+
puts 'Language must be objc or swift.'
|
235
|
+
exit
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
def require_lock_file(project_file)
|
241
|
+
lock_file = LockFile.new(project_file)
|
242
|
+
unless lock_file.exists?
|
243
|
+
puts 'Stronglyboards must first be installed using the install command.'
|
244
|
+
exit
|
245
|
+
end
|
246
|
+
lock_file
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
Stronglyboards.start(ARGV)
|
252
|
+
|
253
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Stronglyboards
|
2
|
+
class LockFile
|
3
|
+
|
4
|
+
def initialize(project_file)
|
5
|
+
@path = File.dirname(project_file) + '/' + LOCK_FILE_NAME
|
6
|
+
end
|
7
|
+
|
8
|
+
def contents
|
9
|
+
# Load the lock file containing configuration
|
10
|
+
file = File.open(@path, 'r')
|
11
|
+
YAML::load(file)
|
12
|
+
end
|
13
|
+
|
14
|
+
def update(options)
|
15
|
+
puts "Writing lock file at #{@path}"
|
16
|
+
File.open(@path, 'w+') do |file|
|
17
|
+
file.write(YAML::dump(options))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete
|
22
|
+
File.delete(@path)
|
23
|
+
end
|
24
|
+
|
25
|
+
def exists?
|
26
|
+
File.exists?(@path)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
LOCK_FILE_NAME = 'Stronglyboards.lock'
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Stronglyboards
|
2
|
+
|
3
|
+
class OutputFile < Struct.new(:file, :is_source)
|
4
|
+
end
|
5
|
+
|
6
|
+
class AbstractSourceGenerator
|
7
|
+
|
8
|
+
protected
|
9
|
+
attr_accessor :prefix
|
10
|
+
|
11
|
+
public
|
12
|
+
def initialize(prefix, output_file_name)
|
13
|
+
@prefix = prefix
|
14
|
+
@storyboards = Array.new
|
15
|
+
|
16
|
+
@implementation_file = File.open(output_file_name, 'w+')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gathers a set of view controller classes from all storyboards
|
20
|
+
protected
|
21
|
+
def view_controller_classes
|
22
|
+
@storyboards.collect { |storyboard|
|
23
|
+
storyboard.view_controllers.collect { |vc| vc.class_name }
|
24
|
+
}.flatten.uniq
|
25
|
+
end
|
26
|
+
|
27
|
+
public
|
28
|
+
def add_storyboard(storyboard)
|
29
|
+
@storyboards.push(storyboard)
|
30
|
+
end
|
31
|
+
|
32
|
+
public
|
33
|
+
def parse_storyboards
|
34
|
+
raise 'This method should be overridden.'
|
35
|
+
end
|
36
|
+
|
37
|
+
public
|
38
|
+
def output_files
|
39
|
+
[OutputFile.new(@implementation_file, true)]
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require_relative 'source_generator'
|
2
|
+
|
3
|
+
module Stronglyboards
|
4
|
+
class SourceGeneratorObjC < AbstractSourceGenerator
|
5
|
+
|
6
|
+
def initialize(prefix, output_file_name)
|
7
|
+
@implementation_file_path = output_file_name + '.m'
|
8
|
+
|
9
|
+
super(prefix, @implementation_file_path)
|
10
|
+
|
11
|
+
@header_file_path = output_file_name + '.h'
|
12
|
+
@header_file = File.open(@header_file_path, 'w+')
|
13
|
+
end
|
14
|
+
|
15
|
+
# Parses the storyboards
|
16
|
+
public
|
17
|
+
def parse_storyboards
|
18
|
+
|
19
|
+
puts "Header: #{@header_file_path}"
|
20
|
+
puts "Implementation: #{@implementation_file_path}"
|
21
|
+
|
22
|
+
# Generate framework and header imports
|
23
|
+
@header_file.write("@import UIKit;\n\n")
|
24
|
+
@implementation_file.write("#import \"#{File.basename(@header_file_path)}\"\n\n")
|
25
|
+
|
26
|
+
@header_file.write("NS_ASSUME_NONNULL_BEGIN\n\n")
|
27
|
+
|
28
|
+
# Generate the base storyboard class
|
29
|
+
base_class_name = create_base_storyboard_class
|
30
|
+
|
31
|
+
# Generate forward declaration of view controller classes
|
32
|
+
view_controller_classes.each do |class_name|
|
33
|
+
@header_file.write("@class #{class_name};\n")
|
34
|
+
end
|
35
|
+
@header_file.write("\n")
|
36
|
+
|
37
|
+
# Generate classes for each storyboard
|
38
|
+
@storyboards.each { |s| create_storyboard_class(s, base_class_name) }
|
39
|
+
|
40
|
+
# Generate the storyboard category
|
41
|
+
create_storyboard_category
|
42
|
+
|
43
|
+
@header_file.write("\n\nNS_ASSUME_NONNULL_END")
|
44
|
+
|
45
|
+
output_files
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def create_base_storyboard_class
|
50
|
+
class_name = "#{@prefix}Stronglyboard"
|
51
|
+
|
52
|
+
# Create the public interface
|
53
|
+
interface = Array.new
|
54
|
+
interface.push("@interface #{class_name} : NSObject")
|
55
|
+
interface.push('@property (nonatomic, strong, readonly) UIStoryboard *storyboard;')
|
56
|
+
interface.push('@end')
|
57
|
+
|
58
|
+
# Create the private interface to expose the storyboard as a read-write property
|
59
|
+
implementation = Array.new
|
60
|
+
implementation.push("@interface #{class_name} ()")
|
61
|
+
implementation.push('@property (nonatomic, strong) UIStoryboard *storyboard;')
|
62
|
+
implementation.push('@end')
|
63
|
+
|
64
|
+
# Create the implementation of the base storyboard class
|
65
|
+
implementation.push("@implementation #{class_name}")
|
66
|
+
implementation.push('- (instancetype)initWithName:(NSString *)name bundle:(NSBundle *)bundleOrNil {')
|
67
|
+
implementation.push("\tself = [super init];")
|
68
|
+
implementation.push("\tif (self) {")
|
69
|
+
implementation.push("\t\t_storyboard = [UIStoryboard storyboardWithName:name bundle:bundleOrNil];")
|
70
|
+
implementation.push("\t}")
|
71
|
+
implementation.push("\treturn self;")
|
72
|
+
implementation.push('}')
|
73
|
+
implementation.push('@end')
|
74
|
+
|
75
|
+
# Convert to string
|
76
|
+
interface = interface.join("\n")
|
77
|
+
implementation = implementation.join("\n")
|
78
|
+
|
79
|
+
@header_file.write(interface + "\n\n")
|
80
|
+
@implementation_file.write(implementation + "\n\n")
|
81
|
+
|
82
|
+
class_name
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generate the class for the provided storyboard
|
86
|
+
private
|
87
|
+
def create_storyboard_class(storyboard, base_class_name)
|
88
|
+
class_name = storyboard.class_name(@prefix)
|
89
|
+
puts "Processing storyboard class #{class_name}."
|
90
|
+
|
91
|
+
interface = Array.new(1, "@interface #{class_name} : #{base_class_name}")
|
92
|
+
implementation = Array.new(1, "@implementation #{class_name}")
|
93
|
+
|
94
|
+
storyboard.view_controllers.each do |vc|
|
95
|
+
if vc.initial_view_controller?
|
96
|
+
method_signature = "- (#{vc.class_name} *)instantiateInitialViewController;"
|
97
|
+
method_body = create_initial_view_controller_instantiation(vc)
|
98
|
+
else
|
99
|
+
method_signature = "- (#{vc.class_name} *)instantiate#{vc.storyboard_identifier}ViewController;"
|
100
|
+
method_body = create_view_controller_instantiation(vc)
|
101
|
+
end
|
102
|
+
|
103
|
+
interface.push(method_signature)
|
104
|
+
implementation.push(method_signature + ' {')
|
105
|
+
implementation.push("\t" + method_body)
|
106
|
+
implementation.push('}')
|
107
|
+
end # view controller iterator
|
108
|
+
|
109
|
+
interface.push('@end')
|
110
|
+
implementation.push('@end')
|
111
|
+
|
112
|
+
# Convert to string
|
113
|
+
interface = interface.join("\n")
|
114
|
+
implementation = implementation.join("\n")
|
115
|
+
|
116
|
+
# Output to files
|
117
|
+
@header_file.write(interface)
|
118
|
+
@header_file.write("\n\n")
|
119
|
+
@implementation_file.write(implementation)
|
120
|
+
@implementation_file.write("\n\n")
|
121
|
+
end
|
122
|
+
|
123
|
+
# Generate the category for UIStoryboard with methods
|
124
|
+
# for each storyboard that has been provided.
|
125
|
+
private
|
126
|
+
def create_storyboard_category
|
127
|
+
interface = Array.new(1, '@interface UIStoryboard (Stronglyboards)')
|
128
|
+
implementation = Array.new(1, '@implementation UIStoryboard (Stronglyboards)')
|
129
|
+
|
130
|
+
@storyboards.each do |storyboard|
|
131
|
+
method_signature = "+(#{storyboard.class_name(@prefix)} *)#{storyboard.lowercase_name(@prefix)}Storyboard;"
|
132
|
+
interface.push(method_signature)
|
133
|
+
implementation.push(method_signature + ' {')
|
134
|
+
implementation.push("\t" + create_storyboard_instantiation(storyboard))
|
135
|
+
implementation.push('}')
|
136
|
+
end
|
137
|
+
interface.push('@end')
|
138
|
+
implementation.push('@end')
|
139
|
+
|
140
|
+
# Convert to a string
|
141
|
+
interface = interface.join("\n")
|
142
|
+
implementation = implementation.join("\n")
|
143
|
+
|
144
|
+
# Output to file
|
145
|
+
puts 'Writing UIStoryboard category.'
|
146
|
+
@header_file.write(interface)
|
147
|
+
@implementation_file.write(implementation)
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
# ---- Helpers ----
|
152
|
+
|
153
|
+
public
|
154
|
+
def output_files
|
155
|
+
super.push OutputFile.new(@header_file, false)
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
def create_storyboard_instantiation(storyboard)
|
160
|
+
class_name = storyboard.class_name(@prefix)
|
161
|
+
"return [[#{class_name} alloc] initWithName:@\"#{storyboard.name}\" bundle:nil];"
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
def create_initial_view_controller_instantiation(view_controller)
|
166
|
+
"return (#{view_controller.class_name} *)[self.storyboard instantiateInitialViewController];"
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
def create_view_controller_instantiation(view_controller)
|
171
|
+
"return (#{view_controller.class_name} *)[self.storyboard instantiateViewControllerWithIdentifier:@\"#{view_controller.storyboard_identifier}\"];"
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require_relative 'source_generator'
|
2
|
+
require_relative 'view_controller'
|
3
|
+
|
4
|
+
module Stronglyboards
|
5
|
+
class SourceGeneratorSwift < AbstractSourceGenerator
|
6
|
+
|
7
|
+
public
|
8
|
+
def initialize(prefix, output_file_name)
|
9
|
+
@implementation_file_path = output_file_name + '.swift'
|
10
|
+
|
11
|
+
super(prefix, @implementation_file_path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_storyboards
|
15
|
+
|
16
|
+
puts "Source file: #{@implementation_file_path}"
|
17
|
+
|
18
|
+
# Generate framework imports
|
19
|
+
@implementation_file.write("import UIKit\n\n")
|
20
|
+
|
21
|
+
# Generate the base storyboard class
|
22
|
+
base_class_name = create_base_storyboard_class
|
23
|
+
|
24
|
+
# Generate classes for each storyboard
|
25
|
+
@storyboards.each { |s| create_storyboard_class(s, base_class_name) }
|
26
|
+
|
27
|
+
# Generate the storyboard category
|
28
|
+
create_storyboard_category
|
29
|
+
|
30
|
+
output_files
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def create_base_storyboard_class
|
35
|
+
class_name = "#{@prefix}Stronglyboard"
|
36
|
+
output = Array.new(1, "class #{class_name} {")
|
37
|
+
output.push "\tlet storyboard: UIStoryboard"
|
38
|
+
output.push "\tinit(name: String, bundle: NSBundle?) {"
|
39
|
+
output.push "\t\tstoryboard = UIStoryboard(name: name, bundle: bundle)"
|
40
|
+
output.push "\t}"
|
41
|
+
output.push '}'
|
42
|
+
|
43
|
+
# Convert to string and write to file
|
44
|
+
output = output.join("\n")
|
45
|
+
@implementation_file.write(output + "\n\n")
|
46
|
+
|
47
|
+
class_name
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generate the class for the provided storyboard
|
51
|
+
private
|
52
|
+
def create_storyboard_class(storyboard, base_class_name)
|
53
|
+
class_name = storyboard.class_name(@prefix)
|
54
|
+
puts "Processing storyboard class #{class_name}."
|
55
|
+
|
56
|
+
output = Array.new(1, "class #{class_name} : #{base_class_name} {")
|
57
|
+
|
58
|
+
storyboard.view_controllers.each do |vc|
|
59
|
+
cast = " as! #{vc.class_name}" unless vc.class_name == ViewController::UIVIEWCONTROLLER
|
60
|
+
if vc.initial_view_controller?
|
61
|
+
cast = '!' if vc.class_name == ViewController::UIVIEWCONTROLLER
|
62
|
+
output.push "\tfunc instantiateInitialViewController() -> #{vc.class_name} {"
|
63
|
+
output.push "\t\treturn self.storyboard.instantiateInitialViewController()#{cast}"
|
64
|
+
else
|
65
|
+
output.push "\tfunc instantiate#{vc.storyboard_identifier}ViewController() -> #{vc.class_name} {"
|
66
|
+
output.push "\t\treturn self.storyboard.instantiateViewControllerWithIdentifier(\"#{vc.storyboard_identifier}\") #{cast}"
|
67
|
+
end
|
68
|
+
output.push "\t}"
|
69
|
+
end # view controller iterator
|
70
|
+
|
71
|
+
# End the storyboard subclass
|
72
|
+
output.push '}'
|
73
|
+
|
74
|
+
# Convert to string
|
75
|
+
output = output.join("\n")
|
76
|
+
|
77
|
+
# Output to files
|
78
|
+
@implementation_file.write(output)
|
79
|
+
@implementation_file.write("\n\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Generate the category for UIStoryboard with methods
|
83
|
+
# for each storyboard that has been provided
|
84
|
+
private
|
85
|
+
def create_storyboard_category
|
86
|
+
output = Array.new(1, 'extension UIStoryboard {')
|
87
|
+
|
88
|
+
@storyboards.each do |storyboard|
|
89
|
+
class_name = storyboard.class_name(@prefix)
|
90
|
+
func_name = "#{storyboard.lowercase_name(@prefix)}Storyboard"
|
91
|
+
output.push "\tclass func #{func_name}() -> #{class_name} {"
|
92
|
+
output.push "\t\treturn #{class_name}(name: \"#{storyboard.name}\", bundle: nil)"
|
93
|
+
output.push "\t}"
|
94
|
+
end
|
95
|
+
|
96
|
+
output.push '}'
|
97
|
+
|
98
|
+
# Convert to a string
|
99
|
+
output = output.join("\n")
|
100
|
+
|
101
|
+
# Output to file
|
102
|
+
puts 'Writing UIStoryboard category.'
|
103
|
+
@implementation_file.write(output)
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require_relative 'view_controller'
|
3
|
+
|
4
|
+
module Stronglyboards
|
5
|
+
class Storyboard
|
6
|
+
|
7
|
+
EXTENSION = '.storyboard'
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
attr_reader :view_controllers
|
11
|
+
|
12
|
+
def initialize(full_path)
|
13
|
+
@name = File.basename(full_path, EXTENSION)
|
14
|
+
|
15
|
+
file = File.open(full_path)
|
16
|
+
@xml = Nokogiri::XML(file)
|
17
|
+
file.close
|
18
|
+
|
19
|
+
@view_controllers = Array.new
|
20
|
+
|
21
|
+
# Find the initial view controller
|
22
|
+
initial_view_controller = find_initial_view_controller
|
23
|
+
@view_controllers.push(initial_view_controller) if initial_view_controller
|
24
|
+
|
25
|
+
# Find other view controllers
|
26
|
+
@view_controllers += find_view_controllers_with_storyboard_identifiers
|
27
|
+
end
|
28
|
+
|
29
|
+
# Searches for the initial view controller
|
30
|
+
private
|
31
|
+
def find_initial_view_controller
|
32
|
+
initial_vc_identifier = @xml.at_xpath('document').attr('initialViewController')
|
33
|
+
view_controller_xml = object_with_identifier(initial_vc_identifier) if initial_vc_identifier
|
34
|
+
if view_controller_xml
|
35
|
+
ViewController.new(view_controller_xml, true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Searches for view controllers
|
40
|
+
private
|
41
|
+
def find_view_controllers_with_storyboard_identifiers
|
42
|
+
view_controllers = @xml.xpath('//scene/objects/*[@storyboardIdentifier]')
|
43
|
+
view_controllers.collect { |xml| ViewController.new(xml) } if view_controllers
|
44
|
+
end
|
45
|
+
|
46
|
+
# --------- Helpers ---------
|
47
|
+
|
48
|
+
private
|
49
|
+
def object_with_identifier(identifier)
|
50
|
+
@xml.at_xpath("//scene/objects/*[@id='#{identifier}']")
|
51
|
+
end
|
52
|
+
|
53
|
+
public
|
54
|
+
def class_name(prefix = nil)
|
55
|
+
prefix + @name + 'Storyboard'
|
56
|
+
end
|
57
|
+
|
58
|
+
def lowercase_name(prefix = nil)
|
59
|
+
lower = @name.dup
|
60
|
+
lower[0] = lower[0].downcase
|
61
|
+
if prefix == nil || prefix.length == 0
|
62
|
+
lower
|
63
|
+
else
|
64
|
+
prefix.downcase + '_' + lower
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Stronglyboards
|
2
|
+
class ViewController
|
3
|
+
|
4
|
+
attr_reader :class_name
|
5
|
+
attr_reader :storyboard_identifier
|
6
|
+
|
7
|
+
UIVIEWCONTROLLER = 'UIViewController'
|
8
|
+
UITABLEVIEWCONTROLLER = 'UITableViewController'
|
9
|
+
UINAVIGATIONCONTROLLER = 'UINavigationController'
|
10
|
+
UITABBARCONTROLLER = 'UITabBarController'
|
11
|
+
UICOLLECTIONVIEWCONTROLLER = 'UICollectionViewController'
|
12
|
+
UISPLITVIEWCONTROLLER = 'UISplitViewController'
|
13
|
+
UIPAGEVIEWCONTROLLER = 'UIPageViewController'
|
14
|
+
|
15
|
+
def initialize(xml, is_initial_view_controller = false)
|
16
|
+
@class_name = xml.attr('customClass') || class_name_from_type(xml)
|
17
|
+
@storyboard_identifier = xml.attr('storyboardIdentifier')
|
18
|
+
@is_initial_view_controller = is_initial_view_controller
|
19
|
+
end
|
20
|
+
|
21
|
+
def initial_view_controller?
|
22
|
+
@is_initial_view_controller
|
23
|
+
end
|
24
|
+
|
25
|
+
# Determines the name of the class from this view controller's type
|
26
|
+
private
|
27
|
+
def class_name_from_type(xml)
|
28
|
+
case xml.name
|
29
|
+
when 'viewController'
|
30
|
+
UIVIEWCONTROLLER
|
31
|
+
when 'tableViewController'
|
32
|
+
UITABLEVIEWCONTROLLER
|
33
|
+
when 'navigationController'
|
34
|
+
UINAVIGATIONCONTROLLER
|
35
|
+
when 'tabBarController'
|
36
|
+
UITABBARCONTROLLER
|
37
|
+
when 'collectionViewController'
|
38
|
+
UICOLLECTIONVIEWCONTROLLER
|
39
|
+
when 'splitViewController'
|
40
|
+
UISPLITVIEWCONTROLLER
|
41
|
+
when 'pageViewController'
|
42
|
+
UIPAGEVIEWCONTROLLER
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -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 'stronglyboards/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'stronglyboards'
|
8
|
+
spec.version = Stronglyboards::VERSION
|
9
|
+
spec.date = '2015-10-16'
|
10
|
+
|
11
|
+
spec.summary = 'A strongly-typed interface for your storyboards, view controllers, and segues.'
|
12
|
+
spec.description = 'Generates a strongly-typed interface for your storyboards, view controllers, and segues.'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.authors = ['Steve Wilford']
|
16
|
+
spec.email = 'steve@offtopic.io'
|
17
|
+
spec.homepage = 'http://stevewilford.co.uk/stronglyboards'
|
18
|
+
|
19
|
+
spec.files = `git ls-files`.split($/)
|
20
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.required_ruby_version = '>= 2.0.0'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'xcodeproj', '>= 0.28.2'
|
27
|
+
spec.add_runtime_dependency 'nokogiri', '>= 1.6.6.2'
|
28
|
+
spec.add_runtime_dependency 'thor', '>= 0.19.1'
|
29
|
+
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stronglyboards
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Steve Wilford
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: xcodeproj
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.28.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.28.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: nokogiri
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.6.6.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.6.6.2
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.19.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.19.1
|
55
|
+
description: Generates a strongly-typed interface for your storyboards, view controllers,
|
56
|
+
and segues.
|
57
|
+
email: steve@offtopic.io
|
58
|
+
executables:
|
59
|
+
- stronglyboards
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- .gitignore
|
64
|
+
- LICENSE.md
|
65
|
+
- README.md
|
66
|
+
- bin/stronglyboards
|
67
|
+
- lib/stronglyboards.rb
|
68
|
+
- lib/stronglyboards/lock_file.rb
|
69
|
+
- lib/stronglyboards/source_generator.rb
|
70
|
+
- lib/stronglyboards/source_generator_objc.rb
|
71
|
+
- lib/stronglyboards/source_generator_swift.rb
|
72
|
+
- lib/stronglyboards/storyboard.rb
|
73
|
+
- lib/stronglyboards/version.rb
|
74
|
+
- lib/stronglyboards/view_controller.rb
|
75
|
+
- stronglyboards.gemspec
|
76
|
+
homepage: http://stevewilford.co.uk/stronglyboards
|
77
|
+
licenses:
|
78
|
+
- MIT
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: 2.0.0
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubyforge_project:
|
96
|
+
rubygems_version: 2.0.14
|
97
|
+
signing_key:
|
98
|
+
specification_version: 4
|
99
|
+
summary: A strongly-typed interface for your storyboards, view controllers, and segues.
|
100
|
+
test_files: []
|