├── .gitignore ├── .travis.yml ├── CHANGES.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── bin └── xcodesnippets ├── features ├── installing_snippet_bundles.feature ├── installing_snippets.feature ├── migrating_existing_bundles.feature ├── step_definitions │ └── global_steps.rb └── support │ ├── env.rb │ ├── fixtures │ ├── Example.snippetbundle │ │ ├── snippet-one.codesnippet │ │ └── snippet-two.codesnippet │ └── example.codesnippet │ └── runner.rb ├── lib ├── xcode_snippets.rb └── xcode_snippets │ ├── bundle.rb │ ├── main.rb │ ├── manifest.rb │ ├── migrator.rb │ ├── snippet.rb │ ├── snippet_manager.rb │ └── version.rb ├── spec ├── bundle_spec.rb ├── migrator_spec.rb ├── snippet_manager_spec.rb └── spec_helper.rb └── xcodesnippets.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | tmp 2 | pkg 3 | rdoc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 1.9.2 2 | 3 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.2.0 2 | * Added migrate command for migrating existing snippets. 3 | 4 | # 0.1.0 5 | * Initial release 6 | * Support for install/install-bundle/uninstall/uninstall-bundle 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | 4 | gem "rspec-expectations", 5 | :git => "git://github.com/lukeredpath/rspec-expectations.git", 6 | :branch => "fuzzy-include-matchers" 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git@github.com:lukeredpath/rspec-expectations.git 3 | revision: 15e2cc34540c296945f603866d0cb6d4c5552197 4 | branch: fuzzy-include-matchers 5 | specs: 6 | rspec-expectations (2.6.0) 7 | diff-lcs (~> 1.1.2) 8 | 9 | PATH 10 | remote: . 11 | specs: 12 | xcodesnippets (0.2.1) 13 | clamp (~> 0.2.1) 14 | highline (~> 1.6.2) 15 | plist (~> 3.1.0) 16 | uuidtools (~> 2.1.2) 17 | 18 | GEM 19 | remote: http://rubygems.org/ 20 | specs: 21 | archive-tar-minitar (0.5.2) 22 | builder (3.0.0) 23 | clamp (0.2.1) 24 | columnize (0.3.4) 25 | cucumber (1.0.1) 26 | builder (>= 2.1.2) 27 | diff-lcs (>= 1.1.2) 28 | gherkin (~> 2.4.5) 29 | json (>= 1.4.6) 30 | term-ansicolor (>= 1.0.5) 31 | diff-lcs (1.1.2) 32 | gherkin (2.4.5) 33 | json (>= 1.4.6) 34 | highline (1.6.2) 35 | json (1.5.3) 36 | linecache19 (0.5.12) 37 | ruby_core_source (>= 0.1.4) 38 | plist (3.1.0) 39 | rake (0.9.2) 40 | rspec (2.6.0) 41 | rspec-core (~> 2.6.0) 42 | rspec-expectations (~> 2.6.0) 43 | rspec-mocks (~> 2.6.0) 44 | rspec-core (2.6.4) 45 | rspec-mocks (2.6.0) 46 | ruby-debug-base19 (0.11.25) 47 | columnize (>= 0.3.1) 48 | linecache19 (>= 0.5.11) 49 | ruby_core_source (>= 0.1.4) 50 | ruby-debug19 (0.11.6) 51 | columnize (>= 0.3.1) 52 | linecache19 (>= 0.5.11) 53 | ruby-debug-base19 (>= 0.11.19) 54 | ruby_core_source (0.1.5) 55 | archive-tar-minitar (>= 0.5.2) 56 | term-ansicolor (1.0.5) 57 | uuidtools (2.1.2) 58 | 59 | PLATFORMS 60 | ruby 61 | 62 | DEPENDENCIES 63 | cucumber 64 | rake 65 | rspec 66 | rspec-expectations! 67 | ruby-debug19 68 | xcodesnippets! 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Luke Redpath 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xcodesnippets - a utility for managing Xcode 4 snippet libraries. [![Build Status](https://secure.travis-ci.org/lukeredpath/xcodesnippets.png)](https://secure.travis-ci.org/lukeredpath/xcodesnippets) 2 | 3 | Xcode 4 introduced code snippets; small, re-usable chunks of code that could be inserted using Xcode's native auto-completion system. 4 | 5 | The problem is, Xcode does little to nothing to help you manage these snippets. There isn't an easy way of sharing them or managing them. `xcodesnippets` aims to make snippets a little bit more manageable, and sharable through the concept of snippet bundles. 6 | 7 | My hope is that one day that this utility becomes obsolete and Xcode itself has better support for managing snippets, including organisation into folders and native installation/activation/deactivation of snippet bundles. To that end, I have [filed a bug](http://openradar.appspot.com/radar?id=1214402) (rdar://9587558) which I encourage you to dupe if you feel this would be beneficial. 8 | 9 | ## Getting started 10 | 11 | `xcodesnippets` is written in Ruby and distributed as a Ruby gem. Most Macs with development tools installed come with a usable version of Ruby and RubyGems although to date, this gem has only been tested against Ruby 1.9.2. If you have a problem, please [file a bug](https://github.com/lukeredpath/xcodesnippets/issues). 12 | 13 | If you aren't familiar with RubyGems, fire up a Terminal and run the following command: 14 | 15 | $ sudo gem install xcodesnippets 16 | 17 | If you are using a tool like [RVM](https://rvm.beginrescueend.com/), the `sudo` will probably be unnecessary. 18 | 19 | ## Migrating your existing snippets 20 | 21 | If you already have some snippets in `~/Library/Developer/Xcode/UserData/CodeSnippets/` you will want to bring these under `xcodesnippets` control before you do anything else. 22 | 23 | Fortunately, `xcodesnippets` will do this for you; not only that, but it will rename each snippet file to use a more meaningful title (the one you gave it within Xcode). 24 | 25 | To migrate your exiting snippets, run: 26 | 27 | $ xcodesnippets migrate 28 | 29 | A list of your existing snippets will be displayed and with your confirmation, they will be copied into the default `xcodesnippet` bundle, removed from the Xcode snippets directory, then re-linked back to the `xcodesnippet` versions (see "How xcodesnippets works" for more). 30 | 31 | ## Installing a code snippet 32 | 33 | Code snippets are distributed as property list files with a `.codesnippet` extension. If you have created any custom code snippets in Xcode 4, you will find them in your home directory, under `~/Library/Developer/Xcode/UserData/CodeSnippets/`. The files are named using GUIDs. Any `codesnippet` file created from within Xcode 4, or manually if you are comfortable editing the files yourself (they are just plists) are installable using `xcodesnippets`. 34 | 35 | To install a snippet, run the following from the terminal: 36 | 37 | $ xcodesnippets install [path-to-snippet-file] 38 | 39 | To install a bundle, run the following: 40 | 41 | $ xcodesnippets install-bundle [path-to-snippet-bundle] 42 | 43 | For a full list of commands and options, run `xcodesnippets --help`. 44 | 45 | ## How xcodesnippets works 46 | 47 | `xcodesnippets` stores all of it's snippets inside snippet bundles. Snippet bundles are simply a directory/package with a `.snippetbundle` extension. When creating and sharing snippets, you are encouraged to give them a suitable name that indicates what the snippet does, rather than using the GUID naming scheme that Xcode uses. 48 | 49 | `xcodesnippets` installs all of it's snippets into `~/Library/Developer/Xcode/UserData/ManagedCodeSnippets`. Standalone snippets are stored in a default snippet bundle. Snippets are then symlinked from their installed location to the Xcode code snippets directory with an appropriate GUID, ensuring that they appear in Xcode (you may need to close your current project or workspace before installed snippets appear). 50 | 51 | ## TODO 52 | 53 | * Installing snippets and bundles directly from a URL 54 | * Activating snippets from outside the managed directory - great for using your own snippets from their local source repos. 55 | * Generating snippet bundles from a folder of snippets ready for distribution 56 | * Commands for listing installed bundles and their contents 57 | * Commands for activating and de-activating individual bundles/snippets 58 | 59 | ## License 60 | 61 | This project is licensed under the terms of the MIT license. 62 | 63 | Copyright (c) 2011 Luke Redpath 64 | 65 | Permission is hereby granted, free of charge, to any person obtaining a copy 66 | of this software and associated documentation files (the "Software"), to deal 67 | in the Software without restriction, including without limitation the rights 68 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 69 | copies of the Software, and to permit persons to whom the Software is 70 | furnished to do so, subject to the following conditions: 71 | 72 | The above copyright notice and this permission notice shall be included in 73 | all copies or substantial portions of the Software. 74 | 75 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 76 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 77 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 78 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 79 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 80 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 81 | THE SOFTWARE. 82 | 83 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'cucumber/rake/task' 2 | require 'rspec/core/rake_task' 3 | require File.join(File.dirname(__FILE__), *%w[lib xcode_snippets/version]) 4 | 5 | Cucumber::Rake::Task.new(:features) 6 | 7 | RSpec::Core::RakeTask.new(:spec) do |t| 8 | t.rspec_opts = "--color" 9 | end 10 | 11 | task :default => [:spec, :features] 12 | 13 | require "rubygems" 14 | require "rubygems/package_task" 15 | 16 | # This builds the actual gem. For details of what all these options 17 | # mean, and other ones you can add, check the documentation here: 18 | # 19 | # http://rubygems.org/read/chapter/20 20 | # 21 | spec = Gem::Specification.new do |s| 22 | 23 | # Change these as appropriate 24 | s.name = "xcodesnippets" 25 | s.version = XcodeSnippets::Version.to_s 26 | s.summary = "A command-line utility for managing Xcode 4 code snippets" 27 | s.author = "Luke Redpath" 28 | s.email = "luke@lukeredpath.co.uk" 29 | s.homepage = "http://lukeredpath.co.uk" 30 | 31 | s.has_rdoc = true 32 | s.extra_rdoc_files = %w(README.md) 33 | s.rdoc_options = %w(--main README.md) 34 | 35 | # Add any extra files to include in the gem 36 | s.files = %w(LICENSE README.md) + Dir.glob("{bin,spec,lib}/**/*") 37 | s.executables = FileList["bin/**"].map { |f| File.basename(f) } 38 | s.require_paths = ["lib"] 39 | 40 | # If you want to depend on other gems, add them here, along with any 41 | # relevant versions 42 | s.add_dependency("clamp", "~> 0.2.1") 43 | s.add_dependency("uuidtools", "~> 2.1.2") 44 | s.add_dependency("plist", "~> 3.1.0") 45 | s.add_dependency("highline", "~> 1.6.2") 46 | 47 | # If your tests use any gems, include them here 48 | s.add_development_dependency("rspec") 49 | s.add_development_dependency("cucumber") 50 | s.add_development_dependency("ruby-debug19") 51 | end 52 | 53 | # This task actually builds the gem. We also regenerate a static 54 | # .gemspec file, which is useful if something (i.e. GitHub) will 55 | # be automatically building a gem for this project. If you're not 56 | # using GitHub, edit as appropriate. 57 | # 58 | # To publish your gem online, install the 'gemcutter' gem; Read more 59 | # about that here: http://gemcutter.org/pages/gem_docs 60 | Gem::PackageTask.new(spec) do |pkg| 61 | pkg.gem_spec = spec 62 | end 63 | 64 | desc "Build the gemspec file #{spec.name}.gemspec" 65 | task :gemspec do 66 | file = File.dirname(__FILE__) + "/#{spec.name}.gemspec" 67 | File.open(file, "w") {|f| f << spec.to_ruby } 68 | end 69 | 70 | desc "Update bundled gems" 71 | task :bundle => :gemspec do 72 | system "bundle" 73 | end 74 | 75 | # If you don't want to generate the .gemspec file, just remove this line. Reasons 76 | # why you might want to generate a gemspec: 77 | # - using bundler with a git source 78 | # - building the gem without rake (i.e. gem build blah.gemspec) 79 | # - maybe others? 80 | task :package => :gemspec 81 | 82 | desc "Build and deploy the gem to RubyGems.org" 83 | task :release => :package do 84 | gem_path = File.join('pkg', spec.file_name) 85 | system "gem push #{gem_path}" 86 | end 87 | -------------------------------------------------------------------------------- /bin/xcodesnippets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib') 3 | 4 | require 'xcode_snippets' 5 | XcodeSnippets::Main.run 6 | -------------------------------------------------------------------------------- /features/installing_snippet_bundles.feature: -------------------------------------------------------------------------------- 1 | Feature: Installing snippet bundles 2 | In order to use groups of related snippets more easily 3 | As an Xcode user 4 | I want be able to install bundles of snippets 5 | 6 | Background: 7 | Given Xcode snippets are stored in "tmp/xcode-snippets" 8 | And installed snippets are stored in "tmp/snippets" 9 | 10 | Scenario: Installing a bundle with a single snippet 11 | Given I have the snippet bundle "tmp/SampleSnippets.snippetbundle" 12 | And the bundle contains the snippet "example-snippet.codesnippet" 13 | When I run xcodesnippets with "install-bundle tmp/SampleSnippets.snippetbundle" 14 | Then the snippet file should be installed to "tmp/snippets/SampleSnippets.snippetbundle/example-snippet.codesnippet" 15 | And the installed snippet files should be symlinked inside "tmp/xcode-snippets" 16 | 17 | Scenario: Uninstalling a snippet bundle 18 | Given I have installed the snippet bundle "tmp/SampleSnippets.snippetbundle" 19 | When I run xcodesnippets with "uninstall-bundle SampleSnippets" 20 | Then the snippet bundle should not exist 21 | And all of the bundle snippet symlinks should be removed 22 | -------------------------------------------------------------------------------- /features/installing_snippets.feature: -------------------------------------------------------------------------------- 1 | Feature: Installing snippets 2 | In order to use other people's snippets 3 | As an Xcode user 4 | I want be able to install snippets to Xcode's snippet folder 5 | 6 | Background: 7 | Given Xcode snippets are stored in "tmp/xcode-snippets" 8 | And installed snippets are stored in "tmp/snippets" 9 | 10 | Scenario: Installing a single snippet 11 | Given I have the snippet file "tmp/example-snippet.codesnippet" 12 | When I run xcodesnippets with "install tmp/example-snippet.codesnippet" 13 | Then the snippet file should be installed to "tmp/snippets/Default.snippetbundle/example-snippet.codesnippet" 14 | And the installed snippet files should be symlinked inside "tmp/xcode-snippets" 15 | 16 | Scenario: Installing multiple snippets 17 | Given I have the snippet file "tmp/example-snippet-one.codesnippet" 18 | And I have the snippet file "tmp/example-snippet-two.codesnippet" 19 | When I run xcodesnippets with "install tmp/example-snippet-one.codesnippet tmp/example-snippet-two.codesnippet" 20 | Then the snippet files should be installed to "tmp/snippets/Default.snippetbundle" 21 | And the installed snippet files should be symlinked inside "tmp/xcode-snippets" 22 | 23 | Scenario: Uninstalling a snippet 24 | Given I have installed the snippet file "tmp/example-snippet.codesnippet" 25 | When I run xcodesnippets with "uninstall example-snippet" 26 | Then the snippet file "tmp/snippets/Default.snippetbundle/example-snippet.codesnippet" should not exist 27 | And it's symlink should be removed 28 | -------------------------------------------------------------------------------- /features/migrating_existing_bundles.feature: -------------------------------------------------------------------------------- 1 | Feature: Migrating existing bundles 2 | In order to start using xcodesnippets with my existing snippets straight away 3 | As an Xcode user 4 | I want to be able to bring all of my existing snippets under xcodesnippets control 5 | 6 | Background: 7 | Given Xcode snippets are stored in "tmp/xcode-snippets" 8 | And installed snippets are stored in "tmp/snippets" 9 | 10 | Scenario: Migrating existing snippets to default bundle 11 | Given I have the existing snippet "8CBED4F4-DD88-4C04-A456-24C786002C0F.codesnippet" 12 | When I run xcodesnippets with "migrate --skip-confirm" 13 | Then the snippet file should be installed to "tmp/snippets/Default.snippetbundle/appledoc class.codesnippet" 14 | And the installed snippet files should be symlinked inside "tmp/xcode-snippets" 15 | -------------------------------------------------------------------------------- /features/step_definitions/global_steps.rb: -------------------------------------------------------------------------------- 1 | require 'ostruct' 2 | 3 | Given /^Xcode snippets are stored in "([^"]*)"$/ do |path| 4 | FileUtils.mkdir_p(path) 5 | XcodeSnippets.xcode_snippets_path = File.expand_path(path) 6 | end 7 | 8 | Given /^installed snippets are stored in "([^"]*)"$/ do |path| 9 | FileUtils.mkdir_p(path) 10 | XcodeSnippets.installation_path = File.expand_path(path) 11 | end 12 | 13 | Given /^I have the snippet file "([^"]*)"$/ do |path| 14 | configuration.snippets ||= [] 15 | configuration.snippets << File.basename(path) 16 | 17 | File.open(path, "w") do |io| 18 | io.write File.read(File.join(File.dirname(__FILE__), *%w[.. support fixtures example.codesnippet])) 19 | end 20 | end 21 | 22 | Given /^I have the snippet bundle "([^"]*)"$/ do |path| 23 | configuration.bundles ||= [] 24 | configuration.bundles << path 25 | FileUtils.mkdir_p(path) 26 | end 27 | 28 | Given /^the bundle contains the snippet "([^"]*)"$/ do |snippet_name| 29 | current_bundle = configuration.bundles.last 30 | 31 | File.open(File.join(current_bundle, snippet_name), "w") do |io| 32 | io.write File.read(File.join(File.dirname(__FILE__), *%w[.. support fixtures example.codesnippet])) 33 | end 34 | end 35 | 36 | Given /^I have installed the snippet file "([^"]*)"$/ do |path| 37 | configuration.installed_snippet = XcodeSnippets::Runner.run("install #{path}").first 38 | end 39 | 40 | Given /^I have installed the snippet bundle "([^"]*)"$/ do |path| 41 | configuration.installed_bundle = XcodeSnippets::Runner.run("install-bundle #{path}") 42 | end 43 | 44 | Given /^I have the existing snippet "([^"]*)"$/ do |snippet_name| 45 | # we need a clean slate for deterministic tests 46 | Dir[File.join(XcodeSnippets.xcode_snippets_path, "*.codesnippet")].each do |file| 47 | FileUtils.rm_rf(file) 48 | end 49 | File.open(File.join(XcodeSnippets.xcode_snippets_path, snippet_name), "w") do |io| 50 | io.write File.read(File.join(File.dirname(__FILE__), *%w[.. support fixtures example.codesnippet])) 51 | end 52 | end 53 | 54 | When /^I run xcodesnippets with "([^"]*)"$/ do |command| 55 | configuration.last_result = XcodeSnippets::Runner.run(command) 56 | 57 | if configuration.last_result.is_a?(StandardError) 58 | raise configuration.last_result 59 | end 60 | end 61 | 62 | Then /^the snippet file should be installed to "([^"]*)"$/ do |path| 63 | if configuration.last_result.is_a?(XcodeSnippets::Bundle) 64 | installed_snippet = configuration.last_result.snippets.first 65 | else 66 | installed_snippet = configuration.last_result.first 67 | end 68 | installed_snippet.path.should == File.expand_path(path) 69 | installed_snippet.should exist 70 | end 71 | 72 | Then /^the snippet files should be installed to "([^"]*)"$/ do |root_path| 73 | if configuration.last_result.is_a?(XcodeSnippets::Bundle) 74 | snippets = configuration.last_result.snippets 75 | else 76 | snippets = configuration.last_result 77 | end 78 | 79 | snippets.each do |snippet| 80 | expected_path = File.join(File.expand_path(root_path), snippet.name) 81 | snippet.path.should == expected_path 82 | snippet.should exist 83 | end 84 | end 85 | 86 | Then /^the installed snippet files should be symlinked inside "([^"]*)"$/ do |dir| 87 | if configuration.last_result.is_a?(XcodeSnippets::Bundle) 88 | snippets = configuration.last_result.snippets 89 | else 90 | snippets = configuration.last_result 91 | end 92 | 93 | snippets.each do |snippet| 94 | File.dirname(snippet.symlink).should == File.expand_path(dir) 95 | snippet.should be_symlinked 96 | end 97 | end 98 | 99 | Then /^the snippet file "([^"]*)" should not exist$/ do |path| 100 | File.exist?(path).should be_false 101 | end 102 | 103 | Then /^it's symlink should be removed$/ do 104 | configuration.installed_snippet.should_not be_symlinked 105 | end 106 | 107 | Then /^the snippet bundle should not exist$/ do 108 | configuration.installed_bundle.should_not exist 109 | end 110 | 111 | Then /^all of the bundle snippet symlinks should be removed$/ do 112 | configuration.installed_bundle.snippets.each do |snippet| 113 | snippet.should_not be_symlinked 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), *%w[.. .. lib xcode_snippets]) 2 | require File.join(File.dirname(__FILE__), *%w[runner]) 3 | 4 | require 'ruby-debug' 5 | 6 | def configuration 7 | @configuration ||= OpenStruct.new 8 | end 9 | -------------------------------------------------------------------------------- /features/support/fixtures/Example.snippetbundle/snippet-one.codesnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDECodeSnippetCompletionPrefix 6 | doc 7 | IDECodeSnippetCompletionScopes 8 | 9 | TopLevel 10 | 11 | IDECodeSnippetContents 12 | /** <#summary#> 13 | 14 | <#description#> 15 | */ 16 | IDECodeSnippetIdentifier 17 | 039A91B7-CB7D-46E8-83A9-1AD49189B85C 18 | IDECodeSnippetLanguage 19 | Xcode.SourceCodeLanguage.Objective-C-Plus-Plus 20 | IDECodeSnippetSummary 21 | Documentation for a class or protocol 22 | IDECodeSnippetTitle 23 | appledoc: class 24 | IDECodeSnippetUserSnippet 25 | 26 | IDECodeSnippetVersion 27 | 2 28 | 29 | 30 | -------------------------------------------------------------------------------- /features/support/fixtures/Example.snippetbundle/snippet-two.codesnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDECodeSnippetCompletionPrefix 6 | doc 7 | IDECodeSnippetCompletionScopes 8 | 9 | TopLevel 10 | 11 | IDECodeSnippetContents 12 | /** <#summary#> 13 | 14 | <#description#> 15 | */ 16 | IDECodeSnippetIdentifier 17 | 039A91B7-CB7D-46E8-83A9-1AD49189B85C 18 | IDECodeSnippetLanguage 19 | Xcode.SourceCodeLanguage.Objective-C-Plus-Plus 20 | IDECodeSnippetSummary 21 | Documentation for a class or protocol 22 | IDECodeSnippetTitle 23 | appledoc: class 24 | IDECodeSnippetUserSnippet 25 | 26 | IDECodeSnippetVersion 27 | 2 28 | 29 | 30 | -------------------------------------------------------------------------------- /features/support/fixtures/example.codesnippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDECodeSnippetCompletionPrefix 6 | doc 7 | IDECodeSnippetCompletionScopes 8 | 9 | TopLevel 10 | 11 | IDECodeSnippetContents 12 | /** <#summary#> 13 | 14 | <#description#> 15 | */ 16 | IDECodeSnippetIdentifier 17 | 039A91B7-CB7D-46E8-83A9-1AD49189B85C 18 | IDECodeSnippetLanguage 19 | Xcode.SourceCodeLanguage.Objective-C-Plus-Plus 20 | IDECodeSnippetSummary 21 | Documentation for a class or protocol 22 | IDECodeSnippetTitle 23 | appledoc class 24 | IDECodeSnippetUserSnippet 25 | 26 | IDECodeSnippetVersion 27 | 2 28 | 29 | 30 | -------------------------------------------------------------------------------- /features/support/runner.rb: -------------------------------------------------------------------------------- 1 | module XcodeSnippets 2 | class Runner 3 | def self.run(command) 4 | return_value = "" 5 | 6 | thread = Thread.fork do 7 | # Clamp::Command.run expects ARGV-style arguments 8 | begin 9 | return_value = XcodeSnippets::Main.run("xcodesnippets", command.split(" ")) 10 | rescue StandardError => e 11 | return_value = e 12 | end 13 | end 14 | 15 | # need to wait until the thread has finished otherwise we 16 | # may get non-deterministic results due to commands starting 17 | # while a previous one has not finished 18 | while thread.alive? 19 | end 20 | 21 | return_value 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/xcode_snippets.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__)) 2 | 3 | require 'fileutils' 4 | require 'xcode_snippets/main' 5 | require 'xcode_snippets/version' 6 | 7 | module XcodeSnippets 8 | DEFAULT_INSTALLATION_PATH = File.expand_path("~/Library/Developer/Xcode/UserData/ManagedCodeSnippets") 9 | DEFAULT_XCODE_SNIPPETS_PATH = File.expand_path("~/Library/Developer/Xcode/UserData/CodeSnippets") 10 | 11 | def self.installation_path 12 | @installation_path || DEFAULT_INSTALLATION_PATH 13 | end 14 | 15 | def self.installation_path=(path) 16 | @installation_path = path 17 | end 18 | 19 | def self.xcode_snippets_path 20 | @xcode_snippets_path || DEFAULT_XCODE_SNIPPETS_PATH 21 | end 22 | 23 | def self.xcode_snippets_path=(path) 24 | @xcode_snippets_path = path 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/xcode_snippets/bundle.rb: -------------------------------------------------------------------------------- 1 | require 'xcode_snippets/snippet' 2 | 3 | module XcodeSnippets 4 | class Bundle 5 | attr_reader :path, :snippets 6 | 7 | def initialize(path) 8 | @path = path 9 | @snippets = [] 10 | load_snippets 11 | end 12 | 13 | class << self 14 | def bundle_named(name, install_directory) 15 | new(File.join(install_directory, "#{name}.snippetbundle")) 16 | end 17 | 18 | def default(directory) 19 | bundle = bundle_named("Default", directory) 20 | 21 | if bundle.exists? 22 | bundle 23 | else 24 | bundle.copy_to(directory) 25 | end 26 | end 27 | end 28 | 29 | def copy_to(directory) 30 | installation_path = File.join(directory, name) 31 | FileUtils.mkdir_p(installation_path) 32 | 33 | self.class.new(installation_path).tap do |copied_bundle| 34 | snippets.each do |snippet| 35 | copied_bundle.add_copy_of_snippet(snippet) 36 | end 37 | end 38 | end 39 | 40 | def name 41 | File.basename(path) 42 | end 43 | 44 | def exists? 45 | File.exist?(path) 46 | end 47 | 48 | def add_copy_of_snippet_from_file(snippet_path) 49 | add_copy_of_snippet Snippet.new(snippet_path) 50 | end 51 | 52 | def add_snippet(snippet) 53 | @snippets << snippet 54 | @snippets.last 55 | end 56 | 57 | def add_copy_of_snippet(snippet) 58 | @snippets << snippet.copy_to_bundle(self) 59 | @snippets.last 60 | end 61 | 62 | def delete 63 | FileUtils.rm_rf(path) 64 | end 65 | 66 | def snippet_named(name) 67 | name += ".codesnippet" if name !~ /\.codesnippet$/ 68 | snippet = Snippet.new(File.join(path, name), self) 69 | snippet.exists? ? snippet : nil 70 | end 71 | 72 | private 73 | 74 | def load_snippets 75 | Dir["#{path}/*.codesnippet"].each do |file| 76 | add_snippet Snippet.new(file, self) 77 | end 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /lib/xcode_snippets/main.rb: -------------------------------------------------------------------------------- 1 | require 'clamp/command' 2 | require 'highline/import' 3 | require 'xcode_snippets/snippet_manager' 4 | require 'xcode_snippets/migrator' 5 | 6 | module XcodeSnippets 7 | class Main < Clamp::Command 8 | 9 | subcommand "install", "Install a single snippet" do 10 | parameter "FILE ...", "Path to code snippet to install" 11 | 12 | def execute 13 | manager.install_snippets_from_paths(file_list) 14 | end 15 | end 16 | 17 | subcommand "install-bundle", "Install a snippet bundle" do 18 | parameter "FILE", "Name of the installed snippet" 19 | 20 | def execute 21 | manager.install_snippet_bundle(file) 22 | end 23 | end 24 | 25 | subcommand "uninstall", "Uninstall a single snippet" do 26 | parameter "NAME", "Name of the installed snippet" 27 | 28 | def execute 29 | manager.uninstall_snippet_named(name) 30 | end 31 | end 32 | 33 | subcommand "uninstall-bundle", "Uninstall a snippet bundle" do 34 | parameter "NAME", "Name of the installed snippet bundle" 35 | 36 | def execute 37 | manager.uninstall_snippet_bundle_named(name) 38 | end 39 | end 40 | 41 | subcommand "migrate", "Migrates existing Xcode snippets to a default bundle" do 42 | option "--skip-confirm", :flag, "Skips confirmation before doing the migration" 43 | 44 | def execute 45 | snippets = migrator.migrate_snippets_from(XcodeSnippets.xcode_snippets_path, self) 46 | migrator.clean_up 47 | snippets 48 | end 49 | 50 | def migrator_should_proceed_with_migration?(migrator, snippets_to_migrate) 51 | prompt = %Q{********************************************************************* 52 | Warning: this will move #{snippets_to_migrate.count} code snippets under xcodesnippets control: 53 | ********************************************************************* 54 | 55 | * #{snippets_to_migrate.map { |s| s.metadata.title}.join("\n* ")} 56 | 57 | Continue? (y/n) 58 | } 59 | if skip_confirm? 60 | return true 61 | end 62 | agree(prompt) 63 | end 64 | end 65 | 66 | private 67 | 68 | def manager 69 | XcodeSnippets::SnippetManager.new(manifest) 70 | end 71 | 72 | def manifest 73 | Manifest.load(XcodeSnippets.installation_path, XcodeSnippets.xcode_snippets_path) 74 | end 75 | 76 | def migrator 77 | @migrator ||= XcodeSnippets::Migrator.new(manager) 78 | end 79 | 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/xcode_snippets/manifest.rb: -------------------------------------------------------------------------------- 1 | require 'plist' 2 | 3 | module XcodeSnippets 4 | class Manifest 5 | attr_reader :snippets_install_path 6 | 7 | def initialize(snippets_install_path, xcode_snippets_install_path, uuid_generator = UUIDGenerator) 8 | @snippets_install_path = snippets_install_path 9 | @xcode_snippets_install_path = xcode_snippets_install_path 10 | @uuid_generator = uuid_generator 11 | @data = {} 12 | end 13 | 14 | def self.load(snippets_install_path, xcode_snippets_install_path, uuid_generator = UUIDGenerator) 15 | new(snippets_install_path, xcode_snippets_install_path, uuid_generator).tap { |manifest| manifest.load } 16 | end 17 | 18 | def add_snippet(snippet) 19 | @data[snippet.key] = snippet.symlink 20 | end 21 | 22 | def add_snippet!(snippet) 23 | add_snippet(snippet) 24 | save 25 | end 26 | 27 | def remove_snippet(snippet) 28 | @data.delete(snippet.key) 29 | end 30 | 31 | def remove_snippet!(snippet) 32 | remove_snippet(snippet) 33 | save 34 | end 35 | 36 | def has_snippet?(snippet) 37 | @data[snippet.key] && @data[snippet.key] == snippet.symlink 38 | end 39 | 40 | def save 41 | File.open(path, "w") do |io| 42 | io.write @data.to_plist 43 | end 44 | end 45 | 46 | def load 47 | @data = Plist.parse_xml(path) || {} 48 | end 49 | 50 | def symlink_for_snippet(snippet) 51 | @data[snippet.key] 52 | end 53 | 54 | def generate_symlink_for_snippet(snippet) 55 | snippet.set_guid!(generate_guid) 56 | File.join(@xcode_snippets_install_path, "#{snippet.guid}.codesnippet") 57 | end 58 | 59 | def generate_guid 60 | @uuid_generator.generate 61 | end 62 | 63 | private 64 | 65 | def path 66 | File.join(@snippets_install_path, "Manifest.plist") 67 | end 68 | 69 | class UUIDGenerator 70 | def self.generate 71 | UUIDTools::UUID.random_create.to_s.upcase 72 | end 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/xcode_snippets/migrator.rb: -------------------------------------------------------------------------------- 1 | module XcodeSnippets 2 | class Migrator 3 | def initialize(snippet_manager) 4 | @snippet_manager = snippet_manager 5 | end 6 | 7 | def migrate_snippets_from(path, confirmation_delegate = nil) 8 | make_temp_dir(path) 9 | snippet_files = Dir[File.join(path, "*.codesnippet")] 10 | 11 | snippets = snippet_files.map do |snippet_path| 12 | XcodeSnippets::Snippet.new(snippet_path) 13 | end 14 | 15 | if confirmation_delegate && confirmation_delegate.respond_to?(:migrator_should_proceed_with_migration?) 16 | return unless confirmation_delegate.migrator_should_proceed_with_migration?(self, snippets) 17 | end 18 | 19 | temp_paths = snippets.map do |snippet| 20 | File.join(@tmp_dir, "#{snippet.metadata.title}.codesnippet").tap do |temp_path| 21 | FileUtils.mv(snippet.path, temp_path) 22 | end 23 | end 24 | 25 | @snippet_manager.install_snippets_from_paths(temp_paths) 26 | end 27 | 28 | def clean_up 29 | FileUtils.rm_rf(@tmp_dir) if @tmp_dir 30 | @tmp_dir = nil 31 | end 32 | 33 | private 34 | 35 | def make_temp_dir(path) 36 | unless @tmp_dir 37 | @tmp_dir = File.join(path, "migrator") 38 | FileUtils.mkdir_p(@tmp_dir) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/xcode_snippets/snippet.rb: -------------------------------------------------------------------------------- 1 | module XcodeSnippets 2 | class Snippet 3 | attr_reader :path, :symlink 4 | 5 | def initialize(path, bundle = nil) 6 | @path = path 7 | @bundle = bundle 8 | end 9 | 10 | def set_guid!(new_guid) 11 | metadata.guid = new_guid 12 | metadata.save_to(path) 13 | end 14 | 15 | def copy_to_bundle(bundle) 16 | FileUtils.cp(path, bundle.path) 17 | self.class.new(File.join(bundle.path, name), bundle) 18 | end 19 | 20 | def activate(manifest) 21 | @symlink = manifest.generate_symlink_for_snippet(self) 22 | FileUtils.symlink(path, symlink) 23 | manifest.add_snippet(self) 24 | end 25 | 26 | def deactivate(manifest) 27 | manifest.remove_snippet(self) 28 | end 29 | 30 | def delete 31 | FileUtils.rm_f(path) 32 | end 33 | 34 | def name 35 | File.basename(@path) 36 | end 37 | 38 | def guid 39 | metadata.guid 40 | end 41 | 42 | def metadata 43 | @metadata ||= MetaData.from_file(path) 44 | end 45 | 46 | def key 47 | @bundle ? "#{@bundle.name}/#{name}" : name 48 | end 49 | 50 | def exists? 51 | File.exist?(path) 52 | end 53 | 54 | def symlinked? 55 | symlink && File.exist?(symlink) 56 | end 57 | 58 | class MetaData 59 | def initialize(data) 60 | @data = data 61 | end 62 | 63 | def self.from_file(path) 64 | raise "Could not parse metadata in file #{path}" unless File.exist?(path) 65 | new(Plist.parse_xml(path)) 66 | end 67 | 68 | def title 69 | @data["IDECodeSnippetTitle"] 70 | end 71 | 72 | def guid 73 | @data["IDECodeSnippetIdentifier"] 74 | end 75 | 76 | def guid=(new_guid) 77 | @data["IDECodeSnippetIdentifier"] = new_guid 78 | end 79 | 80 | def save_to(path) 81 | File.open(path, "w") do |io| 82 | io.write @data.to_plist 83 | end 84 | end 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /lib/xcode_snippets/snippet_manager.rb: -------------------------------------------------------------------------------- 1 | require 'uuidtools' 2 | require 'xcode_snippets/bundle' 3 | require 'xcode_snippets/manifest' 4 | 5 | module XcodeSnippets 6 | class SnippetManager 7 | attr_reader :manifest 8 | 9 | def initialize(manifest) 10 | @manifest = manifest 11 | end 12 | 13 | def install_snippet_from_path(path_to_snippet) 14 | install_snippet default_bundle.add_copy_of_snippet_from_file(path_to_snippet) 15 | end 16 | 17 | def install_snippets_from_paths(snippet_path_list) 18 | snippets = snippet_path_list.map do |path_to_snippet| 19 | default_bundle.add_copy_of_snippet_from_file(path_to_snippet) 20 | end 21 | install_snippets snippets 22 | end 23 | 24 | def install_snippet(snippet) 25 | snippet.activate(manifest) 26 | save_manifest 27 | snippet 28 | end 29 | 30 | def install_snippets(snippets) 31 | snippets.each { |snippet| snippet.activate(manifest) } 32 | save_manifest 33 | snippets 34 | end 35 | 36 | def uninstall_snippet_named(snippet_name) 37 | if snippet = default_bundle.snippet_named(snippet_name) 38 | uninstall_snippet(snippet) 39 | end 40 | end 41 | 42 | def uninstall_snippet(snippet) 43 | snippet.deactivate(manifest) 44 | snippet.delete 45 | save_manifest 46 | end 47 | 48 | def install_snippet_bundle(path_to_bundle) 49 | Bundle.new(path_to_bundle).copy_to(manifest.snippets_install_path).tap do |bundle| 50 | install_snippets(bundle.snippets) 51 | end 52 | end 53 | 54 | def uninstall_snippet_bundle_named(bundle_name) 55 | bundle = Bundle.bundle_named(bundle_name, manifest.snippets_install_path) 56 | bundle.snippets.each { |snippet| uninstall_snippet(snippet) } 57 | bundle.delete 58 | save_manifest 59 | end 60 | 61 | def save_manifest 62 | manifest.save 63 | end 64 | 65 | private 66 | 67 | def default_bundle 68 | Bundle.default(manifest.snippets_install_path) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/xcode_snippets/version.rb: -------------------------------------------------------------------------------- 1 | module XcodeSnippets 2 | module Version 3 | MAJOR = 0 4 | MINOR = 2 5 | TINY = 1 6 | 7 | def self.to_s 8 | "#{MAJOR}.#{MINOR}.#{TINY}" 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/bundle_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), *%w[spec_helper]) 2 | 3 | RSpec::Matchers.define :a_snippet_named do |expected| 4 | match do |actual| 5 | actual.is_a?(XcodeSnippets::Snippet) && (actual.name == expected) 6 | end 7 | end 8 | 9 | describe "A bundle" do 10 | 11 | before do 12 | example_bundle_path = File.join(FIXTURES_PATH, "Example.snippetbundle") 13 | @bundle = XcodeSnippets::Bundle.new(example_bundle_path) 14 | end 15 | 16 | it "has a name" do 17 | @bundle.name.should == "Example.snippetbundle" 18 | end 19 | 20 | it "has a snippet for each codesnippet file in the bundle" do 21 | @bundle.should have(2).snippets 22 | @bundle.snippets.should include(a_snippet_named "snippet-one.codesnippet") 23 | @bundle.snippets.should include(a_snippet_named "snippet-two.codesnippet") 24 | end 25 | 26 | context "adding a snippet" do 27 | 28 | before do 29 | example_snippet = File.join(FIXTURES_PATH, "example.codesnippet") 30 | @snippet = XcodeSnippets::Snippet.new(example_snippet) 31 | @bundle.add_snippet(@snippet) 32 | end 33 | 34 | it "should increase the number of snippets in the bundle" do 35 | @bundle.should have(3).snippets 36 | end 37 | 38 | it "should not change the path of the snippet" do 39 | @bundle.snippets.last.path.should == @snippet.path 40 | end 41 | 42 | end 43 | 44 | context "adding a copy of a snippet" do 45 | 46 | before do 47 | example_snippet = File.join(FIXTURES_PATH, "example.codesnippet") 48 | @snippet = XcodeSnippets::Snippet.new(example_snippet) 49 | @bundle.add_copy_of_snippet(@snippet) 50 | end 51 | 52 | after do 53 | if @bundle.snippets.last.name == "example.codesnippet" 54 | FileUtils.rm(@bundle.snippets.last.path) 55 | end 56 | end 57 | 58 | it "should increase the number of snippets in the bundle" do 59 | @bundle.should have(3).snippets 60 | end 61 | 62 | it "should copy the snippet into the bundle's directory" do 63 | @bundle.snippets.last.path.should_not == @snippet.path 64 | @bundle.snippets.last.path.should == File.join(@bundle.path, @snippet.name) 65 | @bundle.snippets.last.should exist 66 | end 67 | 68 | end 69 | 70 | context "adding a copy of a snippet from a file" do 71 | 72 | before do 73 | snippet_file_path = File.join(FIXTURES_PATH, "example.codesnippet") 74 | @snippet = @bundle.add_copy_of_snippet_from_file(snippet_file_path) 75 | end 76 | 77 | after do 78 | if @bundle.snippets.last.name == "example.codesnippet" 79 | FileUtils.rm(@bundle.snippets.last.path) 80 | end 81 | end 82 | 83 | it "should increase the number of snippets in the bundle" do 84 | @bundle.should have(3).snippets 85 | end 86 | 87 | it "should copy the snippet into the bundle's directory" do 88 | @snippet.path.should == File.join(@bundle.path, @snippet.name) 89 | @snippet.should exist 90 | end 91 | 92 | end 93 | 94 | end 95 | -------------------------------------------------------------------------------- /spec/migrator_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), *%w[spec_helper]) 2 | 3 | describe "Migrator" do 4 | 5 | before do 6 | @manager = mock("manager") 7 | @migrator = XcodeSnippets::Migrator.new(@manager) 8 | create_example_xcode_snippets_directory 9 | end 10 | 11 | context "migrating existing snippets" do 12 | 13 | it "copies each snippet to a temporary directory with a filename that reflects the snippet name" do 14 | @manager.stub(:install_snippets_from_paths) 15 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH) 16 | file_at(temporary_snippet_path_for("appledoc class.codesnippet")).should exist 17 | end 18 | 19 | it "removes the original snippet" do 20 | @manager.stub(:install_snippets_from_paths) 21 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH) 22 | file_at(xcode_snippet_path).should_not exist 23 | end 24 | 25 | it "installs each snippet from it's temporary location" do 26 | @manager.should_receive(:install_snippets_from_paths).with([temporary_snippet_path_for("appledoc class.codesnippet")]) 27 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH) 28 | end 29 | 30 | it "returns the created snippets" do 31 | @manager.stub(:install_snippets_from_paths).and_return(["snippet"]) 32 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH).should == ["snippet"] 33 | end 34 | 35 | end 36 | 37 | context "migrating with a confirmation delegate" do 38 | 39 | before do 40 | @delegate = stub("confirmation-delegate") 41 | end 42 | 43 | it "migrates the snippets if the confirmation delegate returns true" do 44 | @delegate.stub(:migrator_should_proceed_with_migration?).and_return(true) 45 | @manager.should_receive(:install_snippets_from_paths) 46 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH, @delegate) 47 | end 48 | 49 | it "migrates the snippets if the confirmation delegate returns false" do 50 | @delegate.stub(:migrator_should_proceed_with_migration?).and_return(false) 51 | @manager.should_receive(:install_snippets_from_paths).never 52 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH, @delegate) 53 | end 54 | 55 | end 56 | 57 | context "cleaning up after migration" do 58 | 59 | before do 60 | @manager.stub(:install_snippets_from_paths) 61 | @migrator.migrate_snippets_from(XCODE_SNIPPET_PATH) 62 | end 63 | 64 | it "removes the temporary migrator directory" do 65 | @migrator.clean_up 66 | directory(File.join(XCODE_SNIPPET_PATH, "migrator")).should_not exist 67 | end 68 | 69 | end 70 | 71 | private 72 | 73 | def create_example_xcode_snippets_directory 74 | FileUtils.cp(example_snippet_path, xcode_snippet_path) 75 | end 76 | 77 | def temporary_snippet_path_for(name) 78 | File.join(XCODE_SNIPPET_PATH, "migrator", name) 79 | end 80 | 81 | def example_snippet_path 82 | File.join(FIXTURES_PATH, "example.codesnippet") 83 | end 84 | 85 | def xcode_snippet_path 86 | @guid ||= UUIDTools::UUID.timestamp_create 87 | File.join(XCODE_SNIPPET_PATH, "#{@guid}.codesnippet") 88 | end 89 | 90 | end 91 | -------------------------------------------------------------------------------- /spec/snippet_manager_spec.rb: -------------------------------------------------------------------------------- 1 | require File.join(File.dirname(__FILE__), *%w[spec_helper]) 2 | 3 | describe "SnippetManager" do 4 | 5 | before(:each) do 6 | manifest = XcodeSnippets::Manifest.load(SNIPPETS_PATH, XCODE_SNIPPET_PATH, FakeUUIDGenerator) 7 | @manager = XcodeSnippets::SnippetManager.new(manifest) 8 | end 9 | 10 | it "saves it's manifest to disk" do 11 | @manager.save_manifest 12 | File.exist?(File.join(SNIPPETS_PATH, "Manifest.plist")).should be_true 13 | end 14 | 15 | describe "#install_snippet_from_path" do 16 | 17 | before do 18 | snippet_path = File.join(FIXTURES_PATH, "example.codesnippet") 19 | @snippet = @manager.install_snippet_from_path(snippet_path) 20 | end 21 | 22 | it "copies the specified snippet file to it's snippets dir in the default bundle" do 23 | expected_path = File.join(SNIPPETS_PATH, "Default.snippetbundle", "example.codesnippet") 24 | File.exist?(expected_path).should be_true 25 | end 26 | 27 | it "generates a new GUID identifier for the snippet" do 28 | @snippet.guid.should_not be_nil 29 | end 30 | 31 | it "creates a symlink to the installed snippet in the Xcode snippets directory based on the snippet's generated GUID" do 32 | symlink = @manager.manifest.symlink_for_snippet(@snippet) 33 | File.exist?(symlink).should be_true 34 | end 35 | 36 | it "updates it's manifest of installed and activated files" do 37 | @manager.manifest.should have_snippet(@snippet) 38 | end 39 | 40 | it "updates the snippet's metadata to reflect it's generated GUID" do 41 | @snippet.metadata.guid.should == @snippet.guid 42 | end 43 | 44 | end 45 | 46 | describe "#install_snippets_from_paths" do 47 | 48 | before do 49 | snippet_path = File.join(FIXTURES_PATH, "example.codesnippet") 50 | @snippets = @manager.install_snippets_from_paths([snippet_path]) 51 | end 52 | 53 | it "returns an array of all installed snippets" do 54 | @snippets.should have(1).item 55 | @snippets.first.should exist 56 | end 57 | 58 | end 59 | 60 | describe "#uninstall_snippet_named" do 61 | 62 | before do 63 | snippet_path = File.join(FIXTURES_PATH, "example.codesnippet") 64 | @snippet = @manager.install_snippet_from_path(snippet_path) 65 | @symlink = @snippet.symlink 66 | @manager.uninstall_snippet_named("example") 67 | end 68 | 69 | it "removes the snippet file from the default bundle" do 70 | @snippet.should_not exist 71 | end 72 | 73 | it "removes it's GUID symlink from the Xcode snippets directory" do 74 | File.exist?(@symlink).should be_false 75 | end 76 | 77 | it "removes the snippet from the manifest" do 78 | @manager.manifest.should_not have_snippet(@snippet) 79 | end 80 | 81 | end 82 | 83 | describe "#install_snippet_bundle" do 84 | 85 | before do 86 | bundle_path = File.join(FIXTURES_PATH, "Example.snippetbundle") 87 | @bundle = @manager.install_snippet_bundle(bundle_path) 88 | end 89 | 90 | it "creates the snippet bundle in the snippets directory" do 91 | expected_path = File.join(SNIPPETS_PATH, "Example.snippetbundle") 92 | File.directory?(expected_path).should be_true 93 | end 94 | 95 | it "copies all of the bundle's snippets to it's installed location" do 96 | expected_path = File.join(SNIPPETS_PATH, "Example.snippetbundle", "snippet-one.codesnippet") 97 | File.exist?(expected_path).should be_true 98 | end 99 | 100 | it "creates a GUID symlink for each bundle snippet in the Xcode snippets directory" do 101 | @bundle.snippets.each do |snippet| 102 | symlink = @manager.manifest.symlink_for_snippet(snippet) 103 | File.exist?(symlink).should be_true 104 | end 105 | end 106 | 107 | it "updates it's manifest of installed and activated files" do 108 | @bundle.snippets.each do |snippet| 109 | @manager.manifest.should have_snippet(snippet) 110 | end 111 | end 112 | 113 | end 114 | 115 | describe "#uninstall_snippet_bundle_named" do 116 | 117 | before do 118 | bundle_path = File.join(FIXTURES_PATH, "Example.snippetbundle") 119 | @bundle = @manager.install_snippet_bundle(bundle_path) 120 | @manager.uninstall_snippet_bundle_named("Example") 121 | end 122 | 123 | it "removes the snippet bundle from the install path" do 124 | @bundle.should_not exist 125 | end 126 | 127 | end 128 | 129 | end 130 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift(File.dirname(__FILE__) + '/../lib') unless $:.include?(File.dirname(__FILE__) + '/../lib') 2 | 3 | require 'rspec' 4 | require 'ruby-debug' 5 | require 'xcode_snippets' 6 | 7 | RSpec.configure do |config| 8 | config.before(:each) do 9 | setup_testing_environment! 10 | end 11 | end 12 | 13 | TMP_PATH = File.join(File.dirname(__FILE__), *%w[.. tmp specs]) 14 | FIXTURES_PATH = File.join(File.dirname(__FILE__), *%w[.. features support fixtures]) 15 | XCODE_SNIPPET_PATH = File.join(TMP_PATH, "xcode-snippets") 16 | SNIPPETS_PATH = File.join(TMP_PATH, "snippets") 17 | 18 | class FakeUUIDGenerator 19 | def self.seed(uuids) 20 | @uuids = uuids 21 | end 22 | 23 | def self.generate 24 | @uuids.pop 25 | end 26 | end 27 | 28 | class FileQuery 29 | def initialize(path) 30 | @path = path 31 | end 32 | 33 | def exist? 34 | File.exist?(@path) 35 | end 36 | 37 | def to_s 38 | "" 39 | end 40 | end 41 | 42 | def file_at(path) 43 | FileQuery.new(path) 44 | end 45 | 46 | def directory(path) 47 | FileQuery.new(path) 48 | end 49 | 50 | def setup_testing_environment! 51 | [SNIPPETS_PATH, XCODE_SNIPPET_PATH].each do |dir| 52 | FileUtils.rm_rf(dir) && FileUtils.mkdir_p(dir) 53 | end 54 | 55 | # seed the generator with enough UUIDs for testing 56 | FakeUUIDGenerator.seed [ 57 | UUIDTools::UUID.timestamp_create, 58 | UUIDTools::UUID.timestamp_create 59 | ] 60 | end 61 | -------------------------------------------------------------------------------- /xcodesnippets.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{xcodesnippets} 5 | s.version = "0.2.1" 6 | 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | s.authors = ["Luke Redpath"] 9 | s.date = %q{2011-07-18} 10 | s.default_executable = %q{xcodesnippets} 11 | s.email = %q{luke@lukeredpath.co.uk} 12 | s.executables = ["xcodesnippets"] 13 | s.extra_rdoc_files = ["README.md"] 14 | s.files = ["LICENSE", "README.md", "bin/xcodesnippets", "spec/bundle_spec.rb", "spec/migrator_spec.rb", "spec/snippet_manager_spec.rb", "spec/spec_helper.rb", "lib/xcode_snippets/bundle.rb", "lib/xcode_snippets/main.rb", "lib/xcode_snippets/manifest.rb", "lib/xcode_snippets/migrator.rb", "lib/xcode_snippets/snippet.rb", "lib/xcode_snippets/snippet_manager.rb", "lib/xcode_snippets/version.rb", "lib/xcode_snippets.rb"] 15 | s.homepage = %q{http://lukeredpath.co.uk} 16 | s.rdoc_options = ["--main", "README.md"] 17 | s.require_paths = ["lib"] 18 | s.rubygems_version = %q{1.6.2} 19 | s.summary = %q{A command-line utility for managing Xcode 4 code snippets} 20 | 21 | if s.respond_to? :specification_version then 22 | s.specification_version = 3 23 | 24 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 25 | s.add_runtime_dependency(%q, ["~> 0.2.1"]) 26 | s.add_runtime_dependency(%q, ["~> 2.1.2"]) 27 | s.add_runtime_dependency(%q, ["~> 3.1.0"]) 28 | s.add_runtime_dependency(%q, ["~> 1.6.2"]) 29 | s.add_development_dependency(%q, [">= 0"]) 30 | s.add_development_dependency(%q, [">= 0"]) 31 | s.add_development_dependency(%q, [">= 0"]) 32 | s.add_development_dependency(%q, [">= 0"]) 33 | else 34 | s.add_dependency(%q, ["~> 0.2.1"]) 35 | s.add_dependency(%q, ["~> 2.1.2"]) 36 | s.add_dependency(%q, ["~> 3.1.0"]) 37 | s.add_dependency(%q, ["~> 1.6.2"]) 38 | s.add_dependency(%q, [">= 0"]) 39 | s.add_dependency(%q, [">= 0"]) 40 | s.add_dependency(%q, [">= 0"]) 41 | s.add_dependency(%q, [">= 0"]) 42 | end 43 | else 44 | s.add_dependency(%q, ["~> 0.2.1"]) 45 | s.add_dependency(%q, ["~> 2.1.2"]) 46 | s.add_dependency(%q, ["~> 3.1.0"]) 47 | s.add_dependency(%q, ["~> 1.6.2"]) 48 | s.add_dependency(%q, [">= 0"]) 49 | s.add_dependency(%q, [">= 0"]) 50 | s.add_dependency(%q, [">= 0"]) 51 | s.add_dependency(%q, [">= 0"]) 52 | end 53 | end 54 | --------------------------------------------------------------------------------