storyboardlint 0.1.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 774c8e07d47f22213d691de1da856975d002cb78
4
- data.tar.gz: 95178dea2c069f16d324a38337e42e46af9bbc3c
3
+ metadata.gz: cb6bd687ab2eef94fe9d9aaf3fe66d5599c05886
4
+ data.tar.gz: 979f598428de5417814402939b1e456e04997c8f
5
5
  SHA512:
6
- metadata.gz: 7e02928fbb6f30432d8ddf7b2af898927837acd7512f0f3e26af9a1444e0ff7f9edf2773373dcf62c3ec40f9457791bb4e1214fddc61deeba9aa0fadc504cb11
7
- data.tar.gz: e9612e771af5f3e140ccbe31df832ccdec336916dcf1b98ed3f4b619ac42c0cbf063c5a53c51f80c8bf08a48f4e151406e0844578f08cacbbc32b37d1d0078a5
6
+ metadata.gz: 5d0978e1b82ac952ee5ea8e7f3ea3286e769a3b9291e0d06f16dc48dd55ce519b6c9736bc41518d9ec6153e105ed85840f0ef0a3f3e2020ae9e4df23e6781a24
7
+ data.tar.gz: f3cbc4e6c6dfc117cbc7c4a87e4e0cbf74d02cb617bb6312ca2b821a30c4bc1d77a93aa32f9872bb621b2341f9c67206f20ea8cf8c57ea49efc91e620092e4e3
data/README.md CHANGED
@@ -1,4 +1,56 @@
1
1
  StoryboardLint
2
2
  ==============
3
3
 
4
- A lint tool for UIStoryboard to find wrong classes and wrong storyboard/segue/reuse identifiers
4
+ A lint tool for UIStoryboard to find wrong classes and wrong storyboard/segue/reuse identifiers.
5
+
6
+ Background
7
+ ----------
8
+ In iOS development, UIStoryboards are a pain to use [for many reasons](http://stackoverflow.com/questions/9404471/when-to-use-storyboard-and-when-to-use-xibs/19457257#19457257).
9
+ Two big reasons are:
10
+ - You can only use string literals as identifiers for view controllers and segues in your Storyboards. You need those same string literals again in your source code when referencing anything in your Storyboard: So you have your IDs in two or more places and no way to know if the IDs you use in your code actually exist in your Storyboard.
11
+ - You have no way of knowing whether your Storyboard references classes in your source code that don't exist (anymore).
12
+
13
+ Fear Not!
14
+ ---------
15
+ With StoryboardLint, you can finally make sure that your code and your Storyboards are in sync (at least to a certain degree). StoryboardLint does the following things:
16
+
17
+ - Makes sure your `UITableViewCell` and `UICollectionViewCell` reuse identifiers are named according to StoryboardLint's naming convention.
18
+ - Makes sure your Storyboard and Segue identifiers are named according to StoryboardLint's naming convention.
19
+ - Makes sure that all custom classes that are references from your Storyboard actually exist is your code.
20
+ - Makes sure that all string literals in your code that reference reuse identifiers, Storyboard identifiers and Segue identifiers (according to the naming convention) actually exist in your Storyboard(s).
21
+ - Produces output that is parsable by Xcode so you can easily plug StoryboardLint into your build process:
22
+
23
+ **Warnings right in your source code:**
24
+
25
+ ![Xcode Warnings](http://i.imgur.com/G24DJhf.png)
26
+
27
+ **Warnings about bugs in your Storyboard:**
28
+
29
+ ![Xcode Warnings](http://i.imgur.com/rfInSBE.png)
30
+
31
+ Naming Convention
32
+ -----------------
33
+ In order for StoryboardLint to find the string literals in your code that contain reuse/Storyboard/Segue identifiers, you need to prefix them as follows:
34
+
35
+ - Reuse Identifiers start with `ruid_`. Example: `ruid_ProductTableViewCell`.
36
+ - Storyboard Identifiers start with `sb_`. Example: `sb_ProductsViewController`.
37
+ - Segue Identifiers start with `seg_`. Example: `seg_ProductDetailsSegue`.
38
+
39
+ This should not be too much work and it will be worth it. Ideally you already have string constants for these things in your code, so that you only have to edit two places: your string constant in code and the respective value in your Storyboard.
40
+
41
+ Installation
42
+ ------------
43
+ Just install the Ruby Gem:
44
+
45
+ sudo gem install storyboardlint
46
+
47
+ In your Xcode Project, simply add a "Run Script" build phase in order to run StoryboardLint every time you build:
48
+
49
+ 1. Go to your project and select your project's target in the "Targets" section.
50
+ 2. Go to "Build Phases"
51
+ 3. Select from the Menu "Editor -> Add Build Phase -> Add Run Script Build Phase"
52
+ 4. Paste this command into the Run Script section: `storyboardlint "$SRCROOT"`
53
+ 5. That's it!
54
+
55
+
56
+
data/Rakefile CHANGED
@@ -16,7 +16,7 @@ Jeweler::Tasks.new do |gem|
16
16
  gem.homepage = "https://github.com/jfahrenkrug/StoryboardLint"
17
17
  gem.license = "MIT"
18
18
  gem.summary = %Q{A lint tool for UIStoryboards to find wrong classes and wrong storyboard/segue/reuse identifiers}
19
- gem.description = %Q{It's a pain to to keep identifier strings in your UIStoryboard and it your source code in sync. This tool helps you to do just that.}
19
+ gem.description = %Q{It's a pain to to keep identifier strings in your UIStoryboard and in your source code in sync. This tool helps you to do just that.}
20
20
  gem.email = "johannes@springenwerk.com"
21
21
  gem.authors = ["Johannes Fahrenkrug"]
22
22
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.1.1
@@ -2,12 +2,10 @@
2
2
  # Copyright (c) 2014 Johannes Fahrenkrug
3
3
 
4
4
  require 'nokogiri'
5
+ require 'ostruct'
6
+ require 'optparse'
5
7
 
6
8
  module StoryboardLint
7
- SEGUE_ID_PREFIX = "seg_"
8
- STORYBOARD_ID_PREFIX = "sb_"
9
- REUSE_ID_PREFIX = "ruid_"
10
-
11
9
  class StoryboardScanner
12
10
  def initialize(src_root)
13
11
  @src_root = src_root
@@ -66,13 +64,9 @@ module StoryboardLint
66
64
  end
67
65
  end
68
66
 
69
- class SourceScanner
70
- CLASS_REGEX = /@interface\s+([a-zA-Z_]+\w*)/
71
- SEGUE_ID_REGEX = /@"(#{SEGUE_ID_PREFIX}(?:\\"|[^"])+)"/
72
- STORYBOARD_ID_REGEX = /@"(#{STORYBOARD_ID_PREFIX}(?:\\"|[^"])+)"/
73
- REUSE_ID_REGEX = /@"(#{REUSE_ID_PREFIX}(?:\\"|[^"])+)"/
74
-
75
- def initialize(src_root)
67
+ class SourceScanner
68
+ def initialize(src_root, matcher)
69
+ @matcher = matcher
76
70
  @src_root = src_root
77
71
  @scan_performed = false
78
72
  end
@@ -94,22 +88,22 @@ module StoryboardLint
94
88
  source_files.each do |source_file|
95
89
  File.readlines(source_file).each_with_index do |line, idx|
96
90
  # class names
97
- line.scan(CLASS_REGEX).each do |match|
91
+ line.scan(@matcher.class_regex).each do |match|
98
92
  @class_names << {:file => source_file, :line => idx + 1, :class_name => match[0]}
99
93
  end
100
94
 
101
95
  # segue ids
102
- line.scan(SEGUE_ID_REGEX).each do |match|
96
+ line.scan(@matcher.segue_id_regex_source).each do |match|
103
97
  @segue_ids << {:file => source_file, :line => idx + 1, :id => match[0]}
104
98
  end
105
99
 
106
100
  # storyboard ids
107
- line.scan(STORYBOARD_ID_REGEX).each do |match|
101
+ line.scan(@matcher.storyboard_id_regex_source).each do |match|
108
102
  @storyboard_ids << {:file => source_file, :line => idx + 1, :id => match[0]}
109
103
  end
110
104
 
111
105
  # reuse ids
112
- line.scan(REUSE_ID_REGEX).each do |match|
106
+ line.scan(@matcher.reuse_id_regex_source).each do |match|
113
107
  @reuse_ids << {:file => source_file, :line => idx + 1, :id => match[0]}
114
108
  end
115
109
  end
@@ -140,8 +134,67 @@ module StoryboardLint
140
134
  end
141
135
  end
142
136
 
137
+ class Matcher
138
+ DEFAULT_SEGUE_ID_PREFIX = "seg_"
139
+ DEFAULT_STORYBOARD_ID_PREFIX = "sb_"
140
+ DEFAULT_REUSE_ID_PREFIX = "ruid_"
141
+
142
+ def initialize(options)
143
+ options ||= OpenStruct.new
144
+
145
+ @storyboard_id_regex_source = create_source_regex(DEFAULT_STORYBOARD_ID_PREFIX, options.storyboard_prefix, options.storyboard_suffix)
146
+ @storyboard_id_regex_sb = create_storyboard_regex(DEFAULT_STORYBOARD_ID_PREFIX, options.storyboard_prefix, options.storyboard_suffix)
147
+
148
+ @segue_id_regex_source = create_source_regex(DEFAULT_SEGUE_ID_PREFIX, options.segue_prefix, options.segue_suffix)
149
+ @segue_id_regex_sb = create_storyboard_regex(DEFAULT_SEGUE_ID_PREFIX, options.segue_prefix, options.segue_suffix)
150
+
151
+ @reuse_id_regex_source = create_source_regex(DEFAULT_REUSE_ID_PREFIX, options.reuse_prefix, options.reuse_suffix)
152
+ @reuse_id_regex_sb = create_storyboard_regex(DEFAULT_REUSE_ID_PREFIX, options.reuse_prefix, options.reuse_suffix)
153
+ end
154
+
155
+ def create_source_regex(default_prefix, prefix, suffix)
156
+ inner_regex_part = %{(?:\\"|[^"])+}
157
+ if prefix.to_s.empty? and suffix.to_s.empty?
158
+ return /@"(#{default_prefix}#{inner_regex_part})"/
159
+ else
160
+ return /@"(#{prefix}#{inner_regex_part}#{suffix})"/
161
+ end
162
+ end
163
+
164
+ def create_storyboard_regex(default_prefix, prefix, suffix)
165
+ inner_regex_part = %{(?:\\"|[^"])+}
166
+ if prefix.to_s.empty? and suffix.to_s.empty?
167
+ sb = /^#{default_prefix}/
168
+ else
169
+ if !prefix.to_s.empty?
170
+ if !suffix.to_s.empty?
171
+ sb = /^#{prefix}[\w\s]*#{suffix}$/
172
+ else !prefix.to_s.empty?
173
+ sb = /^#{prefix}/
174
+ end
175
+ else
176
+ sb = /#{suffix}$/
177
+ end
178
+ end
179
+
180
+ sb
181
+ end
182
+
183
+ def class_regex
184
+ /@interface\s+([a-zA-Z_]+\w*)/
185
+ end
186
+
187
+ [:storyboard, :segue, :reuse].each do |name|
188
+ [:sb, :source].each do |kind|
189
+ method_name = "#{name}_id_regex_#{kind}"
190
+ define_method(method_name) { instance_variable_get("@#{method_name}") }
191
+ end
192
+ end
193
+ end
194
+
143
195
  class Linter
144
- def initialize(sb_scanner, source_scanner)
196
+ def initialize(sb_scanner, source_scanner, matcher)
197
+ @matcher = matcher
145
198
  @sb_scanner = sb_scanner
146
199
  @source_scanner = source_scanner
147
200
  end
@@ -153,21 +206,13 @@ module StoryboardLint
153
206
  end
154
207
 
155
208
  def check_naming
156
- @sb_scanner.segue_ids.each do |seg_id|
157
- if seg_id[:id] !~ /$#{SEGUE_ID_PREFIX}/
158
- puts "warning: Segue ID '#{seg_id[:id]}' used in #{File.basename(seg_id[:file])} does not start with '#{SEGUE_ID_PREFIX}' prefix."
159
- end
160
- end
161
-
162
- @sb_scanner.storyboard_ids.each do |sb_id|
163
- if sb_id[:id] !~ /$#{STORYBOARD_ID_PREFIX}/
164
- puts "warning: Storyboard ID '#{sb_id[:id]}' used in #{File.basename(sb_id[:file])} does not start with '#{STORYBOARD_ID_PREFIX}' prefix."
165
- end
166
- end
167
-
168
- @sb_scanner.reuse_ids.each do |ru_id|
169
- if ru_id[:id] !~ /$#{REUSE_ID_PREFIX}/
170
- puts "warning: Reuse ID '#{ru_id[:id]}' used in #{File.basename(ru_id[:file])} does not start with '#{REUSE_ID_PREFIX}' prefix."
209
+ [{:items => @sb_scanner.segue_ids, :regex => @matcher.segue_id_regex_sb, :name => 'Segue ID'},
210
+ {:items => @sb_scanner.storyboard_ids, :regex => @matcher.storyboard_id_regex_sb, :name => 'Storyboard ID'},
211
+ {:items => @sb_scanner.reuse_ids, :regex => @matcher.reuse_id_regex_sb, :name => 'Reuse ID'}].each do |data|
212
+ data[:items].each do |item|
213
+ if item[:id] !~ data[:regex]
214
+ puts "warning: #{data[:name]} '#{item[:id]}' used in #{File.basename(item[:file])} does not match '#{data[:regex]}."
215
+ end
171
216
  end
172
217
  end
173
218
  end
@@ -181,39 +226,75 @@ module StoryboardLint
181
226
  end
182
227
 
183
228
  def check_ids
184
- @source_scanner.segue_ids.each do |seg_id|
185
- if !@sb_scanner.segue_ids.map {|sb_seg_id| sb_seg_id[:id]}.include?(seg_id[:id])
186
- puts "#{seg_id[:file]}:#{seg_id[:line]}: warning: Segue ID '#{seg_id[:id]}' could not be found in any Storyboard."
187
- end
188
- end
189
-
190
- @source_scanner.storyboard_ids.each do |sb_id|
191
- if !@sb_scanner.storyboard_ids.map {|sb_sb_id| sb_sb_id[:id]}.include?(sb_id[:id])
192
- puts "#{sb_id[:file]}:#{sb_id[:line]}: warning: Storyboard ID '#{sb_id[:id]}' could not be found in any Storyboard."
193
- end
194
- end
195
-
196
- @source_scanner.reuse_ids.each do |ru_id|
197
- if !@sb_scanner.reuse_ids.map {|sb_ru_id| sb_ru_id[:id]}.include?(ru_id[:id])
198
- puts "#{ru_id[:file]}:#{ru_id[:line]}: warning: Reuse ID '#{ru_id[:id]}' could not be found in any Storyboard."
229
+ [{:method_name => :segue_ids, :name => 'Segue ID'},
230
+ {:method_name => :storyboard_ids, :name => 'Storyboard ID'},
231
+ {:method_name => :reuse_ids, :name => 'Reuse ID'}].each do |data|
232
+ @source_scanner.send(data[:method_name]).each do |source_item|
233
+ if !@sb_scanner.send(data[:method_name]).map {|sb_item| sb_item[:id]}.include?(source_item[:id])
234
+ puts "#{source_item[:file]}:#{source_item[:line]}: warning: #{data[:name]} '#{source_item[:id]}' could not be found in any Storyboard."
235
+ end
199
236
  end
200
237
  end
201
238
  end
202
239
 
203
240
  def self.run!(*args)
204
- puts "StoryboardLint"
205
- puts "by Johannes Fahrenkrug, @jfahrenkrug, springenwerk.com"
206
- puts
241
+ options = OpenStruct.new
242
+
243
+ opt_parser = OptionParser.new do |opts|
244
+ opts.banner = "Usage: storyboardlint <target directory> [options]"
245
+ opts.separator ""
246
+ opts.separator "Options"
247
+
248
+ opts.on("--storyboard-prefix [PREFIX]", "Storyboard IDs have to begin with PREFIX.") do |prefix|
249
+ options.storyboard_prefix = prefix
250
+ end
251
+
252
+ opts.on("--storyboard-suffix [SUFFIX]", "Storyboard IDs have to end with SUFFIX") do |suffix|
253
+ options.storyboard_suffix = suffix
254
+ end
255
+
256
+ opts.on("--segue-prefix [PREFIX]", "Segue IDs have to begin with PREFIX") do |prefix|
257
+ options.segue_prefix = prefix
258
+ end
259
+
260
+ opts.on("--segue-suffix [SUFFIX]", "Segue IDs have to end with SUFFIX") do |suffix|
261
+ options.segue_suffix = suffix
262
+ end
263
+
264
+ opts.on("--reuse-prefix [PREFIX]", "Reuse IDs have to begin with PREFIX") do |prefix|
265
+ options.reuse_prefix = prefix
266
+ end
267
+
268
+ opts.on("--reuse-suffix [SUFFIX]", "Reuse IDs have to end with SUFFIX") do |suffix|
269
+ options.reuse_suffix = suffix
270
+ end
271
+
272
+ # No argument, shows at tail. This will print an options summary.
273
+ # Try it and see!
274
+ opts.on_tail("-h", "--help", "Show this message") do
275
+ puts opts
276
+ exit
277
+ end
207
278
 
208
- if args.size < 1
209
- puts "Usage: storyboardlint <target directory>"
210
- exit
279
+ # Another typical switch to print the version.
280
+ opts.on_tail("--version", "Show version") do
281
+ puts "StoryboardLint v0.1.1"
282
+ exit
283
+ end
211
284
  end
285
+
286
+ if ARGV.length < 1
287
+ puts opt_parser
288
+ exit 0
289
+ end
290
+
291
+ opt_parser.parse(args)
212
292
 
293
+ matcher = StoryboardLint::Matcher.new(options)
213
294
  sb_scanner = StoryboardLint::StoryboardScanner.new(ARGV[0])
214
- source_scanner = StoryboardLint::SourceScanner.new(ARGV[0])
295
+ source_scanner = StoryboardLint::SourceScanner.new(ARGV[0], matcher)
215
296
 
216
- linter = StoryboardLint::Linter.new(sb_scanner, source_scanner)
297
+ linter = StoryboardLint::Linter.new(sb_scanner, source_scanner, matcher)
217
298
  linter.lint
218
299
 
219
300
  return 0
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "storyboardlint"
8
- s.version = "0.1.0"
8
+ s.version = "0.1.1"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Johannes Fahrenkrug"]
12
- s.date = "2014-02-19"
13
- s.description = "It's a pain to to keep identifier strings in your UIStoryboard and it your source code in sync. This tool helps you to do just that."
12
+ s.date = "2014-02-20"
13
+ s.description = "It's a pain to to keep identifier strings in your UIStoryboard and in your source code in sync. This tool helps you to do just that."
14
14
  s.email = "johannes@springenwerk.com"
15
15
  s.executables = ["storyboardlint"]
16
16
  s.extra_rdoc_files = [
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: storyboardlint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johannes Fahrenkrug
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-19 00:00:00.000000000 Z
11
+ date: 2014-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri
@@ -52,7 +52,7 @@ dependencies:
52
52
  - - ~>
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.8.0
55
- description: It's a pain to to keep identifier strings in your UIStoryboard and it
55
+ description: It's a pain to to keep identifier strings in your UIStoryboard and in
56
56
  your source code in sync. This tool helps you to do just that.
57
57
  email: johannes@springenwerk.com
58
58
  executables: