rxcode 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+ gem 'plist'
6
+ gem 'trollop'
7
+ gem 'nokogiri'
8
+
9
+ # Add dependencies to develop your gem here.
10
+ # Include everything needed to run rake, tests, features, etc.
11
+ group :development do
12
+ gem "gemcutter", "~> 0.7.0"
13
+ gem "rspec", "~> 2.6.0"
14
+ gem "bundler", "~> 1.0.0"
15
+ gem "jeweler", "~> 1.6.2"
16
+ gem "rcov", ">= 0"
17
+ end
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2011, Vulpine Labs LLC
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ * Neither the name of the Vulpine Labs, nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,7 @@
1
+ # rxcode
2
+
3
+ RXCode is a ruby interface to XCode, useful for writing scripts to automate activities within an XCode project.
4
+
5
+ ## Copyright
6
+
7
+ Copyright (c) 2011 Vulpine Labs. See LICENSE.txt for further details.
@@ -0,0 +1,54 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "rxcode"
18
+ gem.homepage = "http://github.com/vulpinelabs/rxcode"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{A Ruby interface for working with XCode projects.}
21
+ gem.description = %Q{A Ruby interface for working with XCode projects.}
22
+ gem.email = "christian@nerdyc.com"
23
+ gem.authors = ["Christian Niles"]
24
+
25
+ gem.files = FileList[ 'lib/**/*.rb',
26
+ 'lib/rxcode/templates/**',
27
+ 'lib/rxcode/templates/spec/support/.gitkeep',
28
+ 'bin/*',
29
+ '[A-Z]*'
30
+
31
+ ].to_a - %w[Gemfile.lock]
32
+ gem.executables << 'rxcode'
33
+
34
+ # dependencies defined in Gemfile
35
+ end
36
+ Jeweler::RubygemsDotOrgTasks.new
37
+
38
+ require 'rspec/core'
39
+ require 'rspec/core/rake_task'
40
+ RSpec::Core::RakeTask.new(:spec) do |spec|
41
+ spec.pattern = FileList['spec/**/*_spec.rb']
42
+ end
43
+
44
+ task :default => :spec
45
+
46
+ require 'rake/rdoctask'
47
+ Rake::RDocTask.new do |rdoc|
48
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "rxcode #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.5
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ require "rubygems"
3
+ require "bundler/setup"
4
+
5
+ $: << File.expand_path('../../lib', __FILE__)
6
+
7
+ require 'rxcode'
8
+ require 'rxcode/commands'
9
+ RXCode::Command.run!
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+
3
+ module RXCode
4
+
5
+ VERSION = File.read(File.expand_path('../../VERSION', __FILE__)).strip
6
+
7
+ end
8
+
9
+ require 'rxcode/environment'
10
+ require 'rxcode/preferences'
11
+
12
+ require 'rxcode/workspace'
13
+ require 'rxcode/models'
14
+
15
+ if defined?(MACRUBY_VERSION)
16
+ require 'rxcode/macruby'
17
+ end
@@ -0,0 +1,179 @@
1
+ module RXCode
2
+
3
+ class Command
4
+
5
+ #
6
+ # Initializes the command with a set of +options+ and/or +arguments+. The formal way to initialize a command looks
7
+ # like this, with options followed by arguments:
8
+ #
9
+ # Command.new({ :option => 'value' }, [ 'first arg', 'second arg' ])
10
+ #
11
+ # This is the way it's done using parsed command-line arguments. However, this isn't as intuitive when creating
12
+ # commands programmatically, so the more rubyish constructor signature is also supported:
13
+ #
14
+ # Command.new('first arg', 'second arg', :option => 'value')
15
+ # Command.new('first arg', 'second arg')
16
+ #
17
+ def initialize(*args)
18
+ if args.first.is_a?(Hash)
19
+ @options = args.shift
20
+ @arguments = args.shift
21
+ else
22
+ if args.last.is_a?(Hash)
23
+ @options = args.pop
24
+ else
25
+ @options = {}
26
+ end
27
+
28
+ @arguments = args
29
+ end
30
+
31
+ yield self if block_given?
32
+ end
33
+
34
+ # ===== STREAMS ====================================================================================================
35
+
36
+ attr_accessor :output
37
+ def output
38
+ @output || $>
39
+ end
40
+
41
+ attr_accessor :err
42
+ def err
43
+ @err || $stderr
44
+ end
45
+
46
+ attr_accessor :input
47
+ def input
48
+ @input || $<
49
+ end
50
+
51
+ # ===== OPTIONS ====================================================================================================
52
+
53
+ attr_reader :options
54
+
55
+ # ===== ARGUMENTS ==================================================================================================
56
+
57
+ attr_reader :arguments
58
+
59
+ # ===== XCODE ENVIRONMENT ==========================================================================================
60
+
61
+ def env
62
+ @env ||= RXCode::Environment.new(Dir.pwd)
63
+ end
64
+
65
+ # ===== COMMAND REGISTRATION =======================================================================================
66
+ # Commands are automatically registered when they subclass Command.
67
+
68
+ def self.inherited(subclass)
69
+ self.commands[subclass.display_name] = subclass
70
+ end
71
+
72
+ def self.commands
73
+ @commands ||= {}
74
+ end
75
+
76
+ def self.command_names
77
+ self.commands.keys
78
+ end
79
+
80
+ def self.command_class_for_name(command_name)
81
+ self.commands[command_name]
82
+ end
83
+
84
+ def self.display_name
85
+ self.name.split('::').last.
86
+ gsub(/([A-Z]+)/) { |uppercase| '_' + uppercase.downcase }.
87
+ gsub(/^_/, '').
88
+ gsub(/[^a-z]_/) { |str| str.gsub(/_$/, '') }
89
+ end
90
+
91
+ # ===== COMMAND RUNNING ============================================================================================
92
+
93
+ def run!
94
+ if self.class == Command
95
+ raise "#{self.class.name} is an abstract class."
96
+ else
97
+ raise "#{Command.name}#run! is abstract and should be overridden"
98
+ end
99
+ end
100
+
101
+ def self.run!(args = ARGV)
102
+ require 'trollop'
103
+
104
+ global_options = {}
105
+ command_name = nil
106
+ command_arguments = []
107
+
108
+ parser = self.new_global_option_parser
109
+ Trollop::with_standard_exception_handling parser do
110
+ global_options = parser.parse(args)
111
+ command_arguments = parser.leftovers
112
+ command_name = command_arguments.shift
113
+
114
+ if command_name.nil?
115
+
116
+ if (global_options.keys - [:version, :help]).empty?
117
+ raise Trollop::HelpNeeded
118
+ else
119
+ parser.die("No command given", nil)
120
+ end
121
+
122
+ elsif !RXCode::Command.command_names.include?(command_name)
123
+
124
+ parser.die("Unknown command (#{command_name.inspect})", nil)
125
+
126
+ end
127
+
128
+ end
129
+
130
+ run_command(command_name, command_arguments, global_options)
131
+ end
132
+
133
+ def self.run_command(command_name, command_args = [], global_options = {})
134
+ if command_class = self.command_class_for_name(command_name)
135
+ parser = command_class.new_command_option_parser
136
+
137
+ Trollop::with_standard_exception_handling parser do
138
+
139
+ command_options = parser.parse(command_args)
140
+ command_arguments = parser.leftovers
141
+
142
+ command = command_class.new(command_options, command_arguments)
143
+ command.run!
144
+ end
145
+
146
+ else
147
+ raise "Invalid Command: #{command_name.inspect}"
148
+ end
149
+
150
+ end
151
+
152
+ # ----- COMMAND LINE PARSER ----------------------------------------------------------------------------------------
153
+
154
+ def self.new_global_option_parser
155
+
156
+ Trollop::Parser.new do
157
+ version "rxcode #{RXCode::VERSION}"
158
+ banner <<-END
159
+ A utility for manipulating XCode projects.
160
+
161
+ Usage:
162
+ #{$0} [global options] command [command options]
163
+
164
+ Available Commands: #{::RXCode::Command.command_names.sort.join(', ')}
165
+
166
+ Global Options:
167
+ END
168
+ stop_on_unknown
169
+ end
170
+
171
+ end
172
+
173
+ def self.new_command_option_parser
174
+ Trollop::Parser.new
175
+ end
176
+
177
+ end
178
+
179
+ end
@@ -0,0 +1,4 @@
1
+ require 'rxcode/command'
2
+ require 'rxcode/commands/env'
3
+ require 'rxcode/commands/init'
4
+ require 'rxcode/commands/unwrap'
@@ -0,0 +1,45 @@
1
+ module RXCode
2
+ module Commands
3
+
4
+ #
5
+ # Displays information about the current XCode environment
6
+ #
7
+ class Env < ::RXCode::Command
8
+
9
+ def self.display(env, output=$>)
10
+ output.puts "[ #{env.root} ]"
11
+ output.puts
12
+ workspace_path = env.workspace_path
13
+ output.puts "Workspace: #{workspace_path || '(none)'}"
14
+ output.puts "Build Location: #{env.workspace.build_location || '(none)'}"
15
+ output.puts "Built Products: #{env.workspace.built_products_dir || '(none)'}"
16
+ end
17
+
18
+ def run!
19
+ if arguments.empty?
20
+ self.class.display(Dir.pwd)
21
+ else
22
+ arguments.each do |root|
23
+ env = RXCode::Environment.new(File.expand_path(root))
24
+ self.class.display(env)
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.new_command_option_parser
30
+ Trollop::Parser.new do
31
+ banner <<-TEXT
32
+ Displays information about the current XCode environment, culled from the current directory.
33
+
34
+ Usage:
35
+ #{$0} [global options] env [env_root]
36
+
37
+ Options:
38
+ TEXT
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,74 @@
1
+ module RXCode
2
+ module Commands
3
+
4
+ #
5
+ # Displays information about the current XCode environment
6
+ #
7
+ class Init < ::RXCode::Command
8
+
9
+ def projects
10
+ if arguments.empty?
11
+ [ '.' ]
12
+ else
13
+ arguments
14
+ end
15
+ end
16
+
17
+ TEMPLATE_FILES = [
18
+ 'Gemfile',
19
+ 'Rakefile',
20
+ 'spec/spec_helper.rb',
21
+ 'spec/support/.gitkeep'
22
+ ]
23
+
24
+ def run!
25
+
26
+ templates_path = File.expand_path("../../templates", __FILE__)
27
+
28
+ projects.each do |project_path|
29
+
30
+ if RXCode::Project.is_project_at_path?(project_path) ||
31
+ RXCode::Workspace.is_workspace_at_path?(project_path)
32
+
33
+ project_path = File.dirname(project_path)
34
+
35
+ end
36
+
37
+ TEMPLATE_FILES.each do |template_name|
38
+ template_path = File.join(templates_path, template_name)
39
+ instance_path = File.join(project_path, template_name)
40
+
41
+ if File.exist?(instance_path)
42
+
43
+ output.puts "#{project_path.inspect} already exists"
44
+
45
+ else
46
+ # create any intermediate directories...
47
+ FileUtils.mkdir_p(File.dirname(instance_path))
48
+
49
+ # ...and copy the file contents
50
+ FileUtils.cp(template_path, instance_path)
51
+ end
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ def self.new_command_option_parser
59
+ Trollop::Parser.new do
60
+ banner <<-TEXT
61
+ Initializes the current project or workspace with RXCode.
62
+
63
+ Usage:
64
+ #{$0} [global options] init
65
+
66
+ Options:
67
+ TEXT
68
+ end
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,76 @@
1
+ module RXCode
2
+ module Commands
3
+
4
+ class Unwrap < ::RXCode::Command
5
+
6
+ # ===== COMMAND LINE OPTIONS =======================================================================================
7
+
8
+ def self.new_command_option_parser
9
+ Trollop::Parser.new do
10
+ banner <<-TEXT
11
+ Unwraps archive files, in particular the project.pbxproj file, and prints their contents to standard out as a ruby hash.
12
+
13
+ Usage:
14
+ rxcode [global options] unwrap [options] FILENAME...
15
+
16
+ Options:
17
+ TEXT
18
+
19
+ opt :raw, "Prints the raw archive structure instead of the object structure"
20
+ end
21
+ end
22
+
23
+ # ===== OBJECT UNWRAPPING ==========================================================================================
24
+
25
+ def self.unwrap_object_with_id(objects, archive, object_id)
26
+ if objects.has_key?(object_id)
27
+
28
+ objects[object_id]
29
+
30
+ elsif object_hash = archive.object_hashes[object_id]
31
+
32
+ unwrapped_object = {}
33
+ objects[object_id] = unwrapped_object
34
+
35
+ object_hash.each do |object_key, object_value|
36
+ unwrapped_object[object_key] = unwrap_object_value(objects, archive, object_value)
37
+ end
38
+
39
+ unwrapped_object
40
+ end
41
+ end
42
+
43
+ def self.unwrap_object_value(objects, archive, object_value)
44
+ if object_value.is_a?(String) && archive.object_hashes.has_key?(object_value)
45
+ unwrap_object_with_id(objects, archive, object_value)
46
+ elsif object_value.is_a?(Array) && object_value.all? { |array_member| archive.object_hashes.has_key?(array_member) }
47
+ object_value.map { |array_member| unwrap_object_with_id(objects, archive, array_member) }
48
+ else
49
+ object_value
50
+ end
51
+ end
52
+
53
+ def unwrap_objects?
54
+ !options[:raw]
55
+ end
56
+
57
+ # ===== RUN! =======================================================================================================
58
+
59
+ def run!
60
+ require 'pp'
61
+
62
+ arguments.each do |filename|
63
+ archive = ::RXCode::Archive.new(filename)
64
+ if unwrap_objects?
65
+ unwrapped_dictionary = self.class.unwrap_object_with_id({}, archive, archive.root_object_archive_id)
66
+ PP.pp(unwrapped_dictionary, output)
67
+ else
68
+ PP.pp(archive.archive_hash, output)
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+ end