crafter 0.1.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.
data/bin/crafter ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+
4
+ $:.push File.expand_path("../../lib", __FILE__)
5
+
6
+ if ARGV[0] == 'reset'
7
+ puts 'copying defaults to ~/.crafter.rb'
8
+ root = File.expand_path('.', File.dirname(__FILE__))
9
+ FileUtils.cp("#{root}/../lib/config/default.rb", File.join(Dir.home, '.crafter.rb'))
10
+ end
11
+
12
+ require 'crafter'
13
+ Crafter::setup_project()
data/craft.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'crafter'
6
+ gem.version = '0.1.2'
7
+ gem.authors = ['Krzysztof Zabłocki']
8
+ gem.email = ['merowing2@gmail.com']
9
+ gem.description = %q{CLI for setting up new Xcode projects. Inspired by thoughtbot liftoff.}
10
+ gem.summary = %q{Define your craft rules once, then apply it to all your Xcode projects.}
11
+ gem.homepage = 'https://github.com/krzysztofzablocki/crafter'
12
+
13
+ gem.add_dependency 'xcodeproj', '~> 0.5.5'
14
+ gem.add_dependency 'highline', '~> 1.6'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+ end
@@ -0,0 +1,108 @@
1
+ icon_script = %q[
2
+ commit=`git rev-parse --short HEAD`
3
+ branch=`git rev-parse --abbrev-ref HEAD`
4
+ version=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${INFOPLIST_FILE}"`
5
+
6
+ function processIcon() {
7
+ export PATH=$PATH:/usr/local/bin
8
+ base_file=$1
9
+ if [ ! -f $base_file ]; then return; fi
10
+
11
+ target_file=`echo $base_file | sed "s/_base//"`
12
+ target_path="${CONFIGURATION_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/${target_file}"
13
+
14
+ if [ $CONFIGURATION = "Release" ]; then
15
+ cp ${base_file} $target_path
16
+ return
17
+ fi
18
+
19
+ width=`identify -format %w ${base_file}`
20
+
21
+ convert -background '#0008' -fill white -gravity center -size ${width}x40\
22
+ caption:"${version} ${branch} ${commit}"\
23
+ ${base_file} +swap -gravity south -composite ${target_path}
24
+ }
25
+
26
+ processIcon "Icon_base.png"
27
+ processIcon "Icon_base@2x.png"
28
+ processIcon "Icon_base~iPad.png"
29
+ processIcon "Icon_base@2x~iPad.png"
30
+ ]
31
+
32
+
33
+ test_script = %q[
34
+ # Launch application using ios-sim and set up environment to inject test bundle into application
35
+ # Source: http://stackoverflow.com/a/12682617/504494
36
+
37
+ echo "ENTERING TEST SCRIPT"
38
+
39
+ if [ "$RUN_UNIT_TEST_WITH_IOS_SIM" = "YES" ]; then
40
+ echo "TESTING_"
41
+ test_bundle_path="$BUILT_PRODUCTS_DIR/$PRODUCT_NAME.$WRAPPER_EXTENSION"
42
+ ios-sim launch "$(dirname "$TEST_HOST")" --setenv DYLD_INSERT_LIBRARIES=/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection --setenv XCInjectBundle="$test_bundle_path" --setenv XCInjectBundleInto="$TEST_HOST" --args -SenTest All "$test_bundle_path"
43
+ echo "Finished running tests with ios-sim"
44
+ else
45
+ #"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests"
46
+ fi
47
+ ]
48
+
49
+ Crafter.configure do
50
+ add_platform({:platform => :ios, :deployment => 6.0})
51
+ add_git_ignore
52
+ duplicate_configurations({:adhoc => :debug, :profiling => :debug})
53
+
54
+ set_options %w(
55
+ GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED
56
+ GCC_WARN_MISSING_PARENTHESES
57
+ GCC_WARN_ABOUT_RETURN_TYPE
58
+ GCC_WARN_SIGN_COMPARE
59
+ GCC_WARN_CHECK_SWITCH_STATEMENTS
60
+ GCC_WARN_UNUSED_FUNCTION
61
+ GCC_WARN_UNUSED_LABEL
62
+ GCC_WARN_UNUSED_VALUE
63
+ GCC_WARN_UNUSED_VARIABLE
64
+ GCC_WARN_SHADOW
65
+ GCC_WARN_64_TO_32_BIT_CONVERSION
66
+ GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS
67
+ GCC_WARN_UNDECLARED_SELECTOR
68
+ GCC_WARN_TYPECHECK_CALLS_TO_PRINTF
69
+
70
+ RUN_CLANG_STATIC_ANALYZER
71
+ GCC_TREAT_WARNINGS_AS_ERRORS
72
+ )
73
+
74
+ with :default do
75
+ pods << %w(NSLogger-CocoaLumberjack-connector TestFlightSDK)
76
+
77
+ add_option :networking do
78
+ pods << 'AFNetworking'
79
+ end
80
+
81
+
82
+ add_option :coredata do
83
+ pods << 'MagicalRecord'
84
+ end
85
+
86
+ # icon versioning
87
+ scripts << {:name => 'icon versioning', :script => icon_script}
88
+ icon_rename = proc do |file|
89
+ extension = File.extname(file)
90
+ file_name = File.basename(file, extension)
91
+ File.rename(file, "#{File.dirname(file)}/#{file_name}_base.#{extension}")
92
+ end
93
+
94
+ Dir['**/Icon.png'].each(&icon_rename)
95
+ Dir['**/Icon@2x.png'].each(&icon_rename)
96
+ Dir['**/Icon-72.png'].each(&icon_rename)
97
+ Dir['**/Icon-72@2x.png'].each(&icon_rename)
98
+ end
99
+
100
+
101
+ with :tests do
102
+ add_option :testing do
103
+ pods << 'Kiwi'
104
+ scripts << {:name => 'command line unit tests', :script => test_script}
105
+ end
106
+ end
107
+
108
+ end
data/lib/crafter.rb ADDED
@@ -0,0 +1,103 @@
1
+ require_relative 'project_helper'
2
+ require_relative 'git_helper'
3
+ require_relative 'target_configuration'
4
+
5
+ module Crafter
6
+ extend self
7
+ Crafter::ROOT = File.expand_path('.', File.dirname(__FILE__))
8
+
9
+ @targets = {}
10
+ @add_git_ignore = false
11
+ @platforms = []
12
+
13
+ def configure(&block)
14
+ instance_eval &block
15
+ end
16
+
17
+ def with(name, &block)
18
+ self.find_target_configuration(name).instance_eval(&block)
19
+ end
20
+
21
+ def project
22
+ @project ||= ProjectHelper.new
23
+ end
24
+
25
+ def find_target_configuration(name)
26
+ target = @targets[name]
27
+ target ||= self.project.select_target_for_name name
28
+ target_configuration = TargetConfiguration.new(name, target)
29
+ @targets[name] = target_configuration
30
+ target_configuration
31
+ end
32
+
33
+ def add_platform(platform_hash)
34
+ @platforms << platform_hash
35
+ end
36
+
37
+ def add_git_ignore
38
+ @add_git_ignore = true
39
+ end
40
+
41
+ def duplicate_configurations(duplication_hash)
42
+ @configuration = duplication_hash
43
+ end
44
+
45
+ def set_options(options)
46
+ @options = options
47
+ end
48
+
49
+ def setup_project
50
+ process_optional()
51
+ process_configurations() unless @configuration.empty?
52
+ process_options() unless @options.empty?
53
+ process_git() if @add_git_ignore
54
+ process_pods()
55
+ process_scripts()
56
+ end
57
+
58
+ def process_configurations
59
+ self.project.duplicate_configurations(@configuration)
60
+ end
61
+
62
+ def process_optional
63
+ @targets.each { |_, v| v.process_optional }
64
+ end
65
+
66
+ def process_options
67
+ self.project.enable_options(@options)
68
+ end
69
+
70
+ def process_git
71
+ GitHelper.new.generate_files
72
+ end
73
+
74
+ def process_pods
75
+ File.open('Podfile', File::WRONLY|File::CREAT|File::EXCL) do |f|
76
+
77
+ @platforms.each do |hash|
78
+ name = hash[:platform]
79
+ deployment = hash[:deployment]
80
+ if deployment
81
+ f.puts "platform :#{name}, '#{deployment}'"
82
+ else
83
+ f.puts "platform #{name}"
84
+ end
85
+ end
86
+
87
+ @targets.each { |_, v| v.write_pods(f) }
88
+ end
89
+
90
+ rescue Exception => e
91
+ puts "Skipping pod generation - #{e}"
92
+ end
93
+
94
+ def process_scripts
95
+ @targets.each { |_, v| v.process_scripts(self.project) }
96
+ end
97
+
98
+ if File.exists?(File.join(Dir.home, '.crafter.rb')) then
99
+ load '~/.crafter.rb'
100
+ else
101
+ load "#{Crafter::ROOT}/config/default.rb"
102
+ end
103
+ end
data/lib/git_helper.rb ADDED
@@ -0,0 +1,93 @@
1
+ #Copyright (c) 2012-2013 thoughtbot, inc.
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.
23
+
24
+ GITIGNORE_CONTENTS = <<GITIGNORE
25
+ # OS X Finder
26
+ .DS_Store
27
+
28
+ # Xcode per-user config
29
+ *.mode1
30
+ *.mode1v3
31
+ *.mode2v3
32
+ *.perspective
33
+ *.perspectivev3
34
+ *.pbxuser
35
+ xcuserdata
36
+
37
+ # Build products
38
+ build/
39
+ *.o
40
+ *.LinkFileList
41
+ *.hmap
42
+
43
+ # Automatic backup files
44
+ *~.nib/
45
+ *.swp
46
+ *~
47
+ *.dat
48
+ *.dep
49
+
50
+ # AppCode
51
+ .idea
52
+
53
+ GITIGNORE
54
+
55
+ GITATTRIBUTES_CONTENTS = '*.pbxproj binary merge=union'
56
+
57
+ class GitHelper
58
+ def initialize
59
+ if Dir['*.xcodeproj'].empty?
60
+ puts 'Could not find an Xcode project file. You need to run me from a valid project directory.'
61
+ exit
62
+ end
63
+ end
64
+
65
+ def generate_files
66
+ generate_gitignore
67
+ generate_gitattributes
68
+ end
69
+
70
+ private
71
+
72
+ def generate_gitignore
73
+ write_unique_contents_to_file(GITIGNORE_CONTENTS, '.gitignore')
74
+ end
75
+
76
+ def generate_gitattributes
77
+ write_unique_contents_to_file(GITATTRIBUTES_CONTENTS, '.gitattributes')
78
+ end
79
+
80
+ def write_unique_contents_to_file(contents, filename)
81
+ if File.exists? filename
82
+ current_file_contents = File.read(filename).split("\n")
83
+ else
84
+ current_file_contents = []
85
+ end
86
+
87
+ new_contents = current_file_contents + contents.split("\n")
88
+
89
+ File.open(filename, 'w') do |file|
90
+ file.write(new_contents.uniq.join("\n"))
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,102 @@
1
+ require 'xcodeproj'
2
+ require 'highline/import'
3
+
4
+ class ProjectHelper
5
+ PROJECTS = Dir.glob('*.xcodeproj')
6
+
7
+ def initialize
8
+ @project = Xcodeproj::Project.new(xcode_project_file)
9
+ end
10
+
11
+ def enable_options(options)
12
+ @project.build_configurations.each do |configuration|
13
+ options.each do |option|
14
+ configuration.build_settings[option] = 'YES'
15
+ end
16
+ end
17
+ save_changes
18
+ end
19
+
20
+ def add_shell_script(target, name, script)
21
+ if target.shell_script_build_phases.to_a.index { |phase| phase.name == name }
22
+ puts "Skipping adding shell script for target #{target} as #{name} already exists"
23
+ else
24
+ target.new_shell_script_build_phase(name).shell_script = script
25
+ save_changes
26
+ end
27
+ end
28
+
29
+ def duplicate_configurations(configurations_hash)
30
+ configurations_hash.each do |name, base|
31
+
32
+ @project.targets.each do |target|
33
+ base_configuration, project_configuration = find_configurations(base, target)
34
+
35
+ if !base_configuration || !project_configuration
36
+ puts "unable to find configurations for #{base}"
37
+ next
38
+ end
39
+
40
+ target.build_configurations << clone_configuration(base_configuration, name)
41
+ @project.build_configurations << clone_configuration(project_configuration, name)
42
+ end
43
+ end
44
+
45
+ save_changes
46
+ end
47
+
48
+ def select_target_for_name(name)
49
+ targets = @project.targets.to_a.select { |t| t.name.end_with? name.to_s }
50
+ targets = @project.targets.to_a if targets.empty?
51
+ choose_item("Which target should I use for #{name}?", targets)
52
+ end
53
+
54
+ private
55
+
56
+ def clone_configuration(base_configuration, name)
57
+ build_config = @project.new(Xcodeproj::Project::XCBuildConfiguration)
58
+ build_config.name = name.to_s
59
+ build_config.build_settings = base_configuration
60
+ build_config
61
+ end
62
+
63
+ def find_configurations(base, target)
64
+ base_configuration = target.build_configuration_list.build_configurations.find { |t| t.name.downcase == base.to_s.downcase }
65
+ base_configuration = base_configuration.build_settings if base_configuration
66
+
67
+ project_configuration = @project.build_configurations.find { |t| t.name.downcase == base.to_s.downcase }
68
+ project_configuration = project_configuration.build_settings if project_configuration
69
+ return base_configuration, project_configuration
70
+ end
71
+
72
+ def xcode_project_file
73
+ @xcode_project_file ||= choose_item('Project', PROJECTS)
74
+
75
+ if @xcode_project_file == 'Pods.xcodeproj'
76
+ raise 'Can not run in the Pods directory. $ cd .. maybe?'
77
+ end
78
+
79
+ @xcode_project_file
80
+ end
81
+
82
+ def choose_item(title, objects)
83
+ if objects.empty?
84
+ raise "Could not locate any #{title}s!"
85
+ elsif objects.size == 1
86
+ objects.first
87
+ else
88
+ choose do |menu|
89
+ menu.prompt = title
90
+ objects.map { |object| menu.choice(object) }
91
+ end
92
+ end
93
+ end
94
+
95
+ def available_targets
96
+ @project.targets.to_a.delete_if { |t| t.name.end_with?('Tests') }
97
+ end
98
+
99
+ def save_changes
100
+ @project.save_as xcode_project_file
101
+ end
102
+ end
@@ -0,0 +1,59 @@
1
+ class TargetConfiguration
2
+ attr_accessor :pods
3
+ attr_accessor :scripts
4
+
5
+ def initialize(name, target)
6
+ @name = name
7
+ @target = target
8
+ @pods = []
9
+ @scripts = []
10
+ @options = {}
11
+ end
12
+
13
+ def add_option(name, &block)
14
+ @options[name] = block
15
+ end
16
+
17
+ def process_optional
18
+ @options.each do |key, obj|
19
+ key_string = key.to_s
20
+ if ask_question "do you want to add #{key_string}?"
21
+ raise unless obj.is_a? Proc
22
+ obj.call()
23
+ end
24
+ end
25
+ end
26
+
27
+ def write_pods(f)
28
+ f.puts "target :#{@target.name}, :exclusive => true do"
29
+ pods.flatten.each do |pod|
30
+ f.puts "pod '#{pod}'"
31
+ end
32
+ f.puts 'end'
33
+ end
34
+
35
+ def process_scripts(project)
36
+ scripts.each do |hash|
37
+ project.add_shell_script(@target, hash[:name], hash[:script])
38
+ end
39
+ end
40
+
41
+ def ask_question(question)
42
+ puts question
43
+ get_input()
44
+ end
45
+
46
+ def get_input
47
+ STDOUT.flush
48
+ input = STDIN.gets.chomp
49
+ case input.upcase
50
+ when 'Y'
51
+ true
52
+ when 'N'
53
+ false
54
+ else
55
+ puts 'Please enter Y or N'
56
+ get_input
57
+ end
58
+ end
59
+ end