lib/xcres/builder/resources_builder.rb
require 'xcres/builder/file_builder'
require 'xcres/helper/file_helper'
class XCRes::ResourcesBuilder < XCRes::FileBuilder
include XCRes::FileHelper
BANNER = <<EOS
// generated by xcres
//
// DO NOT EDIT. This file is machine-generated and constantly overwritten.
// See https://github.com/mrackwitz/xcres for more info.
//
EOS
OBJC_COMPILER_KEYWORDS = %w{
auto break case char const continue default do double else enum extern float
for goto if inline int long register restrict return short signed sizeof
static struct switch typedef union unsigned void volatile while
}
SWIFT_COMPILER_KEYWORDS = %w{
class deinit enum extension func import init internal let operator private
protocol public static struct subscript typealias var break case continue
default do else fallthrough for if in return switch where while as
dynamicType false is nil self Self super true
}
SWIFT_EXTENSIONS = <<EOS
public extension %s.Strings {
public var localizedValue: String {
return NSLocalizedString(self.rawValue,
bundle: NSBundle(forClass: R.self),
comment: "")
}
}
EOS
# @return [String]
# the name of the constant in the generated file(s)
attr_accessor :resources_constant_name
# @return [Bool]
# whether the generated resources constant should contain inline
# documentation for each key, true by default
attr_accessor :documented
alias :documented? :documented
# @return [Bool]
# whether Swift code should be generated,
# Objective-C used if false, false by default
attr_accessor :swift
alias :swift? :swift
# @return [Hash{String => {String => String}}]
# the sections, which will been written to the built files
attr_reader :sections
# Initialize a new instance
#
def initialize
@sections = {}
self.documented = true
self.swift = false
end
# Extract resource name from #output_path, if not customized
#
# @return [String]
#
def resources_constant_name
@resources_constant_name ||= basename_without_ext output_path
end
def add_section name, items, options = {}
raise ArgumentError.new 'No items given!' if items.nil?
transformed_items = {}
for key, value in items
transformed_key = transform_key key, options
# Skip invalid key names
if transformed_key.length == 0
logger.warn "Skip invalid key: '%s'. (Was transformed to empty text)", key
next
end
# Skip compiler keywords
compiler_keywords = swift? ? SWIFT_COMPILER_KEYWORDS : OBJC_COMPILER_KEYWORDS
if compiler_keywords.include? transformed_key
logger.warn "Skip invalid key: '%s'. (Was transformed to keyword '%s')", key, transformed_key
next
end
transformed_items[transformed_key] = value
end
@sections[name] = transformed_items
end
def build
super
if swift?
build_and_write_swift
else
build_and_write_objc
end
end
protected
def transform_key key, options
# Split the key into components
components = key.underscore.split /[\_\.\-\/ ]/
# Build the new key incremental
result = ''
for component in components
# Ignore empty components
next unless component.length > 0
# Ignore components which are already contained in the key, if enabled
if options[:shorten_keys]
next unless result.downcase.scan(component).blank?
end
# Clean component from non alphanumeric characters
clean_component = component.gsub /[^a-zA-Z0-9]/, ''
# Skip if empty
next unless clean_component.length > 0
if result.length == 0
result += clean_component
else
result += clean_component[0].upcase + clean_component[1..-1]
end
end
# If the first character is not a letter, prefix it with an underscore
result.gsub(/^[^_a-z]/i, '_\0')
end
def build_and_write_swift
# Build file contents and write them to disk
write_file_eventually "#{output_path}.swift", (build_contents do |swift_file|
build_swift_contents swift_file
end)
end
def build_and_write_objc
# Build file contents and write them to disk
write_file_eventually "#{output_path}.h", (build_contents do |h_file|
build_header_contents h_file
end)
write_file_eventually "#{output_path}.m", (build_contents do |m_file|
build_impl_contents m_file
end)
end
def build_header_contents h_file
h_file.writeln BANNER
h_file.writeln
h_file.writeln '#import <Foundation/Foundation.h>'
h_file.writeln
h_file.writeln 'FOUNDATION_EXTERN const struct %s {' % resources_constant_name
h_file.section do |struct|
enumerate_sections do |section_key, enumerate_keys|
struct.writeln 'struct %s {' % section_key
struct.section do |section_struct|
enumerate_keys.call do |key, value, comment|
if documented?
section_struct.writeln '/// %s' % (comment || value) #unless comment.nil?
end
section_struct.writeln '__unsafe_unretained NSString *%s;' % key
end
end
struct.writeln '} %s;' % section_key
end
end
h_file.writeln '} %s;' % resources_constant_name
end
def build_impl_contents m_file
m_file.writeln BANNER
m_file.writeln
m_file.writeln '#import "R.h"'
m_file.writeln
m_file.writeln 'const struct %s %s = {' % [resources_constant_name, resources_constant_name]
m_file.section do |struct|
enumerate_sections do |section_key, enumerate_keys|
struct.writeln '.%s = {' % section_key
struct.section do |section_struct|
enumerate_keys.call do |key, value|
section_struct.writeln '.%s = @"%s",' % [key, value]
end
end
struct.writeln '},'
end
end
m_file.writeln '};'
end
def build_swift_contents swift_file
swift_file.writeln BANNER
swift_file.writeln 'public class %s {' % resources_constant_name
swift_file.section do |struct|
enumerate_sections do |section_key, enumerate_keys|
struct.writeln 'public enum %s: String {' % section_key
struct.section do |section_struct|
enumerate_keys.call do |key, value, comment|
if documented?
section_struct.writeln '/// %s' % (comment || value) #unless comment.nil?
end
section_struct.writeln 'case %s = "%s"' % [key, value]
end
end
struct.writeln '}'
end
end
swift_file.writeln '}'
swift_file.writeln
swift_file.writeln SWIFT_EXTENSIONS % resources_constant_name
end
def enumerate_sections
# Iterate sections ordered by key
for section_key, section_content in @sections.sort
next if section_content.length == 0
# Pass section key and block to yield the keys ordered
proc = Proc.new do |&block|
for key, value in section_content.sort
if value.is_a? Hash
block.call key, value[:value], value[:comment]
else
block.call key, value, nil
end
end
end
yield section_key, proc
end
end
end