Skip to content

Instantly share code, notes, and snippets.

@ebaker355
Last active August 29, 2015 14:26
Show Gist options
  • Save ebaker355/d282b22abc6a92aa6767 to your computer and use it in GitHub Desktop.
Save ebaker355/d282b22abc6a92aa6767 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
#
# ExtractStoryboardIdentifiers.rb
#
# By Eric Baker.
# 25/August/2015
#
# Call this script from a Run Script build phase:
# Shell: /bin/sh
#
# "${SRCROOT}/ExtractStoryboardIdentifiers.rb" "${SRCROOT}" "${SRCROOT}/App" AAA [--swift]
#
# The first arg is the project path, which will be searched for storyboard files
# The second arg is the output path
# The third arg is the class prefix
#
# The generated files will be AAAStoryboardIdentifiers.h/.m
# You should add these files to your Xcode project after they have been generated
# by this script for the first time.
#
require 'English'
# TODO: Better parsing of cmd line args
project_dir = ARGV[0]
output_path = ARGV[1]
class_prefix = ARGV[2]
format_as_swift = false
ARGV.each { |arg|
format_as_swift = true if arg.eql? "--swift"
}
class Storyboard
attr_reader :name
attr_reader :filename
attr_reader :path
attr_reader :segueIdentifiers
attr_reader :storyboardIdentifiers
def self.find_storyboard_files_in_path(path)
storyboards = []
Dir.glob("#{path}/**/*.storyboard").each { |item|
name = File.basename(item).gsub '.storyboard', ''
path = File.dirname(item)
s = Storyboard.new(name, path)
storyboards << s
}
storyboards
end
def initialize(name, path)
@name = name
@name = "#{name}Storyboard" unless @name.end_with? "Storyboard"
@filename = name
@path = path
@segueIdentifiers = []
@storyboardIdentifiers = []
end
def name_with_language
if !self.language.empty?
"#{@name}_#{self.language}"
else
@name
end
end
def language
language = ""
# If the path contains a .lproj component, extract the language from it.
if @path.include? '.lproj'
@path.split('/').each { |item|
if item.include? '.lproj'
language = item.gsub '.lproj', ''
break
end
}
end
language = "" if language.eql? "Base"
language
end
def file_contents
if @file_contents.nil?
@file_contents = File.read(File.join(@path, "#{@filename}.storyboard"))
end
@file_contents
end
def segue_identifiers
self.file_contents.scan(/<segue.*identifier="(\w+)"/).flatten.uniq.sort
end
def storyboard_identifiers
self.file_contents.scan(/storyboardIdentifier="(\w+)"/).flatten.uniq.sort
end
def reuse_identifiers
self.file_contents.scan(/reuseIdentifier="(\w+)"/).flatten.uniq.sort
end
def to_s
"#{self.class.name} -> @name `#{@name}`;\n@path `#{@path}`;\nlanguage `#{self.language}`;\nsegue_identifiers `#{self.segue_identifiers}`;\nstoryboard_identifiers `#{self.storyboard_identifiers}`;\nreuse_identifiers `#{self.reuse_identifiers}`"
end
end
class SourceFileGenerator
def initialize(path, file_name, storyboards)
@path = path
@file_name = file_name
@storyboards = storyboards
end
def file_path
File.join(@path, @file_name)
end
def header_file_path
"#{self.file_path}.h"
end
def implementation_file_path
"#{self.file_path}.m"
end
def swift_file_path
"#{self.file_path}.swift"
end
def create_header_file
header = []
header << "// This file was generated by a run script. Do not edit."
header << ""
header << "#import <Foundation/Foundation.h>"
@storyboards.sort! { |l,r| l.name_with_language <=> r.name_with_language }
@storyboards.each { |sb|
header << ""
header << "// #{sb.name_with_language}"
if sb.reuse_identifiers.count > 0
header << ""
header << "FOUNDATION_EXPORT const struct #{sb.name_with_language}_ReuseIdentifiers {"
sb.reuse_identifiers.each { |identifier|
header << " __unsafe_unretained NSString *#{identifier};"
}
header << "} #{sb.name_with_language}_ReuseIdentifiers;"
end
if sb.segue_identifiers.count > 0
header << ""
header << "FOUNDATION_EXPORT const struct #{sb.name_with_language}_SegueIdentifiers {"
sb.segue_identifiers.each { |identifier|
header << " __unsafe_unretained NSString *#{identifier};"
}
header << "} #{sb.name_with_language}_SegueIdentifiers;"
end
if sb.storyboard_identifiers.count > 0
header << ""
header << "FOUNDATION_EXPORT const struct #{sb.name_with_language}_StoryboardIdentifiers {"
sb.storyboard_identifiers.each { |identifier|
header << " __unsafe_unretained NSString *#{identifier};"
}
header << "} #{sb.name_with_language}_StoryboardIdentifiers;"
end
}
header << ""
File.write self.header_file_path, header.join($INPUT_RECORD_SEPARATOR)
end
def create_implementation_file
impl = []
impl << "// This file was generated by a run script. Do not edit."
impl << ""
impl << "#import \"#{@file_name}.h\""
@storyboards.sort! { |l,r| l.name_with_language <=> r.name_with_language }
@storyboards.each { |sb|
impl << ""
impl << "// #{sb.name_with_language}"
if sb.reuse_identifiers.count > 0
impl << ""
impl << "const struct #{sb.name_with_language}_ReuseIdentifiers #{sb.name_with_language}_ReuseIdentifiers = {"
sb.reuse_identifiers.each { |identifier|
impl << " .#{identifier} = @\"#{identifier}\","
}
impl << "};"
end
if sb.segue_identifiers.count > 0
impl << ""
impl << "const struct #{sb.name_with_language}_SegueIdentifiers #{sb.name_with_language}_SegueIdentifiers = {"
sb.segue_identifiers.each { |identifier|
impl << " .#{identifier} = @\"#{identifier}\","
}
impl << "};"
end
if sb.storyboard_identifiers.count > 0
impl << ""
impl << "const struct #{sb.name_with_language}_StoryboardIdentifiers #{sb.name_with_language}_StoryboardIdentifiers = {"
sb.storyboard_identifiers.each { |identifier|
impl << " .#{identifier} = @\"#{identifier}\","
}
impl << "};"
end
}
impl << ""
File.write self.implementation_file_path, impl.join($INPUT_RECORD_SEPARATOR)
end
def create_swift_file
lines = []
lines << "// This file was generated by a run script. Do not edit."
lines << ""
lines << "import Foundation"
@storyboards.sort! { |l,r| l.name_with_language <=> r.name_with_language }
@storyboards.each { |sb|
lines << ""
lines << "// #{sb.name_with_language}"
lines << ""
structName = sb.name_with_language
structName += "_" if !sb.language.empty?
lines << "struct #{structName}Identifiers {"
if sb.reuse_identifiers.count > 0
lines << ""
lines << " enum Reuse : String {"
sb.reuse_identifiers.each { |identifier|
lines << " case #{identifier} = \"#{identifier}\""
}
lines << " }"
end
if sb.segue_identifiers.count > 0
lines << ""
lines << " enum Segue : String {"
sb.segue_identifiers.each { |identifier|
lines << " case #{identifier} = \"#{identifier}\""
}
lines << " }"
end
if sb.storyboard_identifiers.count > 0
lines << ""
lines << " enum ViewController : String {"
sb.storyboard_identifiers.each { |identifier|
lines << " case #{identifier} = \"#{identifier}\""
}
lines << " }"
end
lines << "}" # end struct
}
lines << ""
File.write self.swift_file_path, lines.join($INPUT_RECORD_SEPARATOR)
end
end
storyboards = Storyboard.find_storyboard_files_in_path(project_dir)
if storyboards.count > 0
generator = SourceFileGenerator.new(output_path, "#{class_prefix}StoryboardIdentifiers", storyboards)
if format_as_swift
generator.create_swift_file
else
generator.create_header_file
generator.create_implementation_file
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment