synx 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +46 -0
  3. data/.travis.yml +20 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +70 -0
  7. data/Rakefile +2 -0
  8. data/bin/synx +18 -0
  9. data/lib/synx.rb +13 -0
  10. data/lib/synx/abstract_object.rb +44 -0
  11. data/lib/synx/pbx_file_reference.rb +70 -0
  12. data/lib/synx/pbx_group.rb +126 -0
  13. data/lib/synx/pbx_native_target.rb +15 -0
  14. data/lib/synx/pbx_variant_group.rb +20 -0
  15. data/lib/synx/project.rb +100 -0
  16. data/lib/synx/tabber.rb +35 -0
  17. data/lib/synx/version.rb +3 -0
  18. data/spec/dummy/dummy.xcodeproj/project.pbxproj +534 -0
  19. data/spec/dummy/dummy.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  20. data/spec/dummy/dummy/AlreadySynced/Core Data.xcdatamodeld/.xccurrentversion +8 -0
  21. data/spec/dummy/dummy/AlreadySynced/Core Data.xcdatamodeld/Core Data 2.xcdatamodel/contents +4 -0
  22. data/spec/dummy/dummy/AlreadySynced/Core Data.xcdatamodeld/Core Data 3.xcdatamodel/contents +4 -0
  23. data/spec/dummy/dummy/AlreadySynced/Core Data.xcdatamodeld/Core Data 4.xcdatamodel/contents +4 -0
  24. data/spec/dummy/dummy/AlreadySynced/Core Data.xcdatamodeld/Core Data.xcdatamodel/contents +4 -0
  25. data/spec/dummy/dummy/AlreadySynced/FolderNotInXcodeProj/AnotherFileNotInXcodeProj.h +0 -0
  26. data/spec/dummy/dummy/AlreadySynced/FolderNotInXcodeProj/NSObject+abc.h +13 -0
  27. data/spec/dummy/dummy/AlreadySynced/FolderNotInXcodeProj/NSObject+abc.m +13 -0
  28. data/spec/dummy/dummy/AlreadySynced/FolderNotInXcodeProj/Wowwww.h +13 -0
  29. data/spec/dummy/dummy/AlreadySynced/FolderNotInXcodeProj/Wowwww.m +13 -0
  30. data/spec/dummy/dummy/AlreadySynced/Woot.h +13 -0
  31. data/spec/dummy/dummy/AlreadySynced/Woot.m +13 -0
  32. data/spec/dummy/dummy/AlreadySynced/stuff.xml +0 -0
  33. data/spec/dummy/dummy/FileNotInXcodeProj.h +0 -0
  34. data/spec/dummy/dummy/ManyFiles.h +13 -0
  35. data/spec/dummy/dummy/ManyFiles.m +13 -0
  36. data/spec/dummy/dummy/Wow.h +13 -0
  37. data/spec/dummy/dummy/Wow.m +13 -0
  38. data/spec/dummy/dummy/data.json +0 -0
  39. data/spec/dummy/dummy/dummy-Prefix.pch +9 -0
  40. data/spec/dummy/dummy/dummy.h +13 -0
  41. data/spec/dummy/dummy/dummy.m +13 -0
  42. data/spec/dummy/dummy/image-not-in-xcodeproj.png +0 -0
  43. data/spec/dummy/dummy/image.png +0 -0
  44. data/spec/dummy/dummyTests/dummyTests-Info.plist +22 -0
  45. data/spec/dummy/dummyTests/dummyTests-prefix.pch +15 -0
  46. data/spec/dummy/dummyTests/dummyTests.m +34 -0
  47. data/spec/dummy/dummyTests/en.lproj/InfoPlist.strings +2 -0
  48. data/spec/spec_helper.rb +11 -0
  49. data/spec/synx/expected_file_structure.yml +44 -0
  50. data/spec/synx/expected_group_structure.yml +37 -0
  51. data/spec/synx/project_spec.rb +218 -0
  52. data/spec/synx/tabber_spec.rb +64 -0
  53. data/synx.gemspec +33 -0
  54. metadata +251 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38ba9b910838a673bb8e0199f562abae2e140126
4
+ data.tar.gz: f9a300779af02f69433fa27e033cad0d293d97a6
5
+ SHA512:
6
+ metadata.gz: 4d9fe16a2d37b84481703837be549b1c5a0ba4156d9968675d00ed69dc98917cbaef6ea5ec04db1076f6bb21555c03abf14e27fa99fd28024379fd2dbf4ce63d
7
+ data.tar.gz: c7ce4e21c6e7768e3ff765aa7a9332169593477a761ffa1d2234b0d8df69c6276752ba712a898e8d5f89fb525be7c92c8a86ee62ad0accfbfd61effce33d8e2f
data/.gitignore ADDED
@@ -0,0 +1,46 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+
24
+ # Xcode
25
+ #
26
+ build/
27
+ *.pbxuser
28
+ !default.pbxuser
29
+ *.mode1v3
30
+ !default.mode1v3
31
+ *.mode2v3
32
+ !default.mode2v3
33
+ *.perspectivev3
34
+ !default.perspectivev3
35
+ xcuserdata
36
+ *.xccheckout
37
+ *.moved-aside
38
+ DerivedData
39
+ *.hmap
40
+ *.ipa
41
+ *.xcuserstate
42
+ *.DS_Store
43
+
44
+
45
+ # Spec
46
+ spec/test_dummy/
data/.travis.yml ADDED
@@ -0,0 +1,20 @@
1
+ language: objective-c
2
+ script: bundle exec rspec
3
+
4
+ # Sets Travis to run the Ruby specs on OS X machines which are required to
5
+ # build the native extensions of Xcodeproj.
6
+
7
+ env:
8
+ - RVM_RUBY_VERSION=system
9
+ - RVM_RUBY_VERSION=2.1.1
10
+
11
+ before_install:
12
+ - curl http://curl.haxx.se/ca/cacert.pem -o /usr/local/share/cacert.pem
13
+ - source ~/.rvm/scripts/rvm
14
+ - if [[ $RVM_RUBY_VERSION != 'system' ]]; then rvm install $RVM_RUBY_VERSION; fi
15
+ - rvm use $RVM_RUBY_VERSION
16
+ - if [[ $RVM_RUBY_VERSION == 'system' ]]; then export ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future; fi
17
+ - if [[ $RVM_RUBY_VERSION == 'system' ]]; then sudo gem install bundler --no-ri --no-rdoc; else gem install bundler --no-ri --no-rdoc; fi
18
+
19
+ install:
20
+ - bundle install --without=documentation --path ./travis_bundle_dir
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in synx.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Mark Larsen
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.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ [![Gem Version](https://badge.fury.io/rb/synx.svg)](http://badge.fury.io/rb/synx)
2
+ ![Travis](https://travis-ci.org/venmo/synx.svg?branch=master)
3
+
4
+ ![synx logo](https://raw.githubusercontent.com/venmo/synx/marklarr/dev/docs/images/synx-logo.png?token=760261__eyJzY29wZSI6IlJhd0Jsb2I6dmVubW8vc3lueC9tYXJrbGFyci9kZXYvZG9jcy9pbWFnZXMvc3lueC1sb2dvLnBuZyIsImV4cGlyZXMiOjE0MDE5MzExNDF9--6c8a22318eaafed2185cb16d39189dcadb75c742)
5
+
6
+ A command-line tool that automagically reorganizes your Xcode project folder.
7
+
8
+ ![synx gif](https://raw.githubusercontent.com/venmo/synx/marklarr/dev/docs/images/synx.gif?token=760261__eyJzY29wZSI6IlJhd0Jsb2I6dmVubW8vc3lueC9tYXJrbGFyci9kZXYvZG9jcy9pbWFnZXMvc3lueC5naWYiLCJleHBpcmVzIjoxNDAxODU2NzAyfQ%3D%3D--fc7d8546f3d4860df9024b1ee82ea13b86a2da88)
9
+
10
+ ##### Xcode
11
+
12
+ ![synx Xcode](https://raw.githubusercontent.com/venmo/synx/marklarr/dev/docs/images/synx-Xcode.jpg?token=760261__eyJzY29wZSI6IlJhd0Jsb2I6dmVubW8vc3lueC9tYXJrbGFyci9kZXYvZG9jcy9pbWFnZXMvc3lueC1YY29kZS5qcGciLCJleHBpcmVzIjoxNDAxOTMxMDY5fQ%3D%3D--969e312f6ee33430855c495f25d9f5ff78fa9e96)
13
+
14
+ ##### Finder
15
+
16
+ ![synx finder before/after](https://raw.githubusercontent.com/venmo/synx/marklarr/dev/docs/images/synx-finder-before-after.png?token=760261__eyJzY29wZSI6IlJhd0Jsb2I6dmVubW8vc3lueC9tYXJrbGFyci9kZXYvZG9jcy9pbWFnZXMvc3lueC1maW5kZXItYmVmb3JlLWFmdGVyLnBuZyIsImV4cGlyZXMiOjE0MDE5MzEwOTd9--8cff7616e4af2f6f2eed624623092745184c0235)
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'synx'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install synx
31
+
32
+ ## Usage
33
+
34
+ ### Basic
35
+
36
+ **WARNING: Make sure that your project is backed up through source control before doing anything**
37
+
38
+ Execute the command on your project to have it reorganize the files on the file system:
39
+
40
+ $ synx path/to/my/project.xcodeproj
41
+
42
+ It may have confused cocoapods. Pod install, if you use them:
43
+
44
+ $ pod
45
+
46
+ You're good to go!
47
+
48
+ ### Advanced
49
+
50
+ Synx supports the following options:
51
+
52
+ ```
53
+ --prune, -p remove source files and image resources that are not referenced by the the xcode project
54
+ --no-default-exclusions doesn't use the default exclusions of /Libraries, /Frameworks, and /Products
55
+ --exclusion, -e EXCLUSION ignore an Xcode group while syncing
56
+ ```
57
+
58
+ OCMock, for example, could have done:
59
+
60
+ $ synx -p -e=OCMock/Core -e=OCKMockTests Source/OCMock.xcodeproj
61
+
62
+ if they wanted to not sync the `OCMock/Core` and `OCMockTests` groups, and also remove (`-p`) any image/source files found by Synx that weren't ever referenced by any groups in Xcode.
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it ( https://github.com/[my-github-username]/synx/fork )
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/synx ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'clamp'
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'synx')
5
+
6
+ Clamp do
7
+
8
+ parameter "xcodeproj", "Path to the xcodeproj", :attribute_name => :xcodeproj_path
9
+ option ["--prune", "-p"], :flag, "remove source files and image resources that are not referenced by the the xcode project"
10
+ option "--no-default-exclusions", :flag, "doesn't use the default exclusions of /Libraries, /Frameworks, and /Products"
11
+ option ["--exclusion", "-e"], "EXCLUSION", "ignore an Xcode group while syncing", :multivalued => true
12
+
13
+ def execute
14
+ project = Synx::Project.open(xcodeproj_path)
15
+ project.sync(:prune => prune?, :no_default_exclusions => no_default_exclusions?, :group_exclusions => exclusion_list)
16
+ end
17
+
18
+ end
data/lib/synx.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "synx/version"
2
+ require "synx/project"
3
+ require "synx/pbx_file_reference"
4
+ require "synx/pbx_group"
5
+ require "synx/abstract_object"
6
+ require "synx/pbx_native_target"
7
+ require "synx/pbx_variant_group"
8
+ require "synx/tabber"
9
+
10
+ require "colored"
11
+
12
+ module Synx
13
+ end
@@ -0,0 +1,44 @@
1
+ require 'xcodeproj'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class AbstractObject
7
+
8
+ def basename
9
+ name || path || Pathname(real_path).basename.to_s
10
+ end
11
+
12
+ def referring_groups
13
+ referrers.select { |ref| ref.instance_of?(Xcodeproj::Project::Object::PBXGroup) }
14
+ end
15
+
16
+ def work_pathname
17
+ # hierarchy path has a leading '/' that will break path concatenation
18
+ @work_pathname ||= project.work_root_pathname + hierarchy_path[1..-1]
19
+ end
20
+
21
+ def ensure_internal_consistency(group)
22
+ @removed_from_groups = []
23
+ if referring_groups.count > 1
24
+ # Files should only have one referring group -- this is an internal consistency issue if there is more than 1.
25
+ # Just remove all referring groups but the one we're syncing with
26
+
27
+ referring_groups.each do |rg|
28
+ unless rg == group
29
+ rg.remove_reference(self) unless rg == group
30
+ @removed_from_groups << rg.hierarchy_path
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+
37
+ def sync(group)
38
+ raise NotImplementedError
39
+ end
40
+
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,70 @@
1
+ require 'xcodeproj'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class PBXFileReference
7
+
8
+ def sync(group)
9
+ if should_sync?
10
+ if should_move?
11
+ FileUtils.mv(real_path.to_s, group.work_pathname.to_s)
12
+ # TODO: move out to abstract_object
13
+ self.source_tree = "<group>"
14
+ self.path = real_path.basename.to_s
15
+ else
16
+ # Don't move this file around -- it's not even inside the structure. Just fix the relative reference
17
+ self.path = real_path.relative_path_from((project.work_pathname_to_pathname(group.work_pathname))).to_s
18
+ end
19
+ change_build_settings_reference
20
+
21
+ output
22
+ else
23
+ Synx::Tabber.puts "skipped #{basename}".red
24
+ end
25
+ end
26
+
27
+ def output
28
+ build_settings_ammended = "(build settings ammended: #{@setting_keys_changed.join(", ")})" if @setting_keys_changed.count > 0
29
+ removed_from_groups = "(had multiple parent groups, removed from groups: #{@removed_from_groups.join(", ")})" if @removed_from_groups.count > 0
30
+ str_output = "#{basename} #{build_settings_ammended} #{removed_from_groups}"
31
+ str_output = str_output.yellow if removed_from_groups || build_settings_ammended
32
+ Synx::Tabber.puts str_output
33
+ end
34
+
35
+ def should_sync?
36
+ # Don't sync files that don't exist or are Apple/Build stuff
37
+ real_path.exist? && !(real_path.to_s =~ /^\$\{(SDKROOT|DEVELOPER_DIR|BUILT_PRODUCTS_DIR)\}/)
38
+ end
39
+ private :should_sync?
40
+
41
+ def should_move?
42
+ # Don't move these files around -- they're not even inside the structure. Just fix the relative references.
43
+ !(real_path.to_s =~ /\.xcodeproj$/) && project.pathname_is_inside_root_pathname?(real_path)
44
+ end
45
+ private :should_move?
46
+
47
+ # Fixes things like pch, info.plist references being changed
48
+ def change_build_settings_reference
49
+ @setting_keys_changed = []
50
+ return unless basename =~ /\.(pch|plist)$/
51
+
52
+ project.targets.each do |t|
53
+ t.each_build_settings do |bs|
54
+ ["INFOPLIST_FILE", "GCC_PREFIX_HEADER"].each do |setting_key|
55
+ setting_value = bs[setting_key]
56
+ if setting_value == real_path.relative_path_from(project.root_pathname).to_s
57
+ bs[setting_key] = hierarchy_path[1..-1]
58
+ @setting_keys_changed << setting_key
59
+ end
60
+ end if bs
61
+ end
62
+ end
63
+
64
+ @setting_keys_changed.uniq!
65
+ end
66
+
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,126 @@
1
+ require 'xcodeproj'
2
+
3
+ module Xcodeproj
4
+ class Project
5
+ module Object
6
+ class PBXGroup
7
+
8
+ def sync(group)
9
+ ensure_internal_consistency(group)
10
+ if excluded_from_sync?
11
+ Synx::Tabber.puts "#{basename}/ (excluded)".yellow
12
+ else
13
+ Synx::Tabber.puts "#{basename}/".green
14
+ Synx::Tabber.increase
15
+
16
+ squash_duplicate_file_references
17
+ work_pathname.mkpath
18
+ files.each { |pbx_file| pbx_file.sync(self) }
19
+ all_groups.each { |group| group.sync(self) }
20
+ sync_path
21
+
22
+ Synx::Tabber.decrease
23
+ end
24
+ end
25
+
26
+ def excluded_from_sync?
27
+ project.group_exclusions.include?(hierarchy_path)
28
+ end
29
+
30
+ def move_entries_not_in_xcodeproj
31
+ if excluded_from_sync?
32
+ Synx::Tabber.puts "#{basename}/ (excluded)".yellow
33
+ else
34
+ Synx::Tabber.puts "#{basename}/".green
35
+ Synx::Tabber.increase
36
+ Dir[real_path.to_s + "/*"].each do |entry|
37
+ entry_pathname = real_path + entry
38
+ unless has_entry?(entry_pathname)
39
+ handle_unused_entry(entry_pathname)
40
+ end
41
+ end
42
+ all_groups.each(&:move_entries_not_in_xcodeproj)
43
+ Synx::Tabber.decrease
44
+ end
45
+ end
46
+
47
+ def sync_path
48
+ self.path = basename
49
+ self.source_tree = "<group>"
50
+ end
51
+ private :sync_path
52
+
53
+ def has_entry?(entry_pathname)
54
+ %W(. ..).include?(entry_pathname.basename.to_s) || children.any? do |child|
55
+ child.real_path.cleanpath == entry_pathname.realpath.cleanpath
56
+ end
57
+ end
58
+ private :has_entry?
59
+
60
+ def all_groups
61
+ groups | version_groups | variant_groups
62
+ end
63
+
64
+ def variant_groups
65
+ children.select { |child| child.instance_of?(Xcodeproj::Project::Object::PBXVariantGroup) }
66
+ end
67
+ private :variant_groups
68
+
69
+ def handle_unused_entry(entry_pathname)
70
+ if entry_pathname.directory?
71
+ project.pathname_to_work_pathname(entry_pathname).mkdir
72
+ # recurse
73
+ Synx::Tabber.puts entry_pathname.basename.to_s.green
74
+ Synx::Tabber.increase
75
+ entry_pathname.children.each { |child| handle_unused_entry(child) }
76
+ Synx::Tabber.decrease
77
+ elsif entry_pathname.file?
78
+ handle_unused_file(entry_pathname)
79
+ end
80
+ end
81
+ private :handle_unused_entry
82
+
83
+ def handle_unused_file(file_pathname)
84
+ prune_file_extensions = %W(.h .m .xib .mm .c .png .jpg .jpeg)
85
+ is_file_to_prune = prune_file_extensions.include?(file_pathname.extname.downcase)
86
+
87
+ if is_file_to_prune && project.prune
88
+ Synx::Tabber.puts "#{file_pathname.basename} (removed source/image file that is not referenced by the Xcode project)".red
89
+ return
90
+ elsif !project.prune || !is_file_to_prune
91
+ FileUtils.mv(file_pathname.realpath, project.pathname_to_work_pathname(file_pathname.parent).realpath)
92
+ if is_file_to_prune
93
+ Synx::Tabber.puts "#{file_pathname.basename} (source/image file that is not referenced by the Xcode project)".yellow
94
+ else
95
+ Synx::Tabber.puts file_pathname.basename
96
+ end
97
+ end
98
+ end
99
+ private :handle_unused_file
100
+
101
+ def squash_duplicate_file_references
102
+ files.each { |f| f.ensure_internal_consistency(self) }
103
+
104
+ grouped_by_path = files.group_by do |file|
105
+ file.real_path.cleanpath
106
+ end
107
+
108
+ duplicates = grouped_by_path.select do |file_path, files|
109
+ files.count > 1
110
+ end
111
+
112
+ duplicates.each do |file_path, files|
113
+ file = files.last
114
+ # Removes all references (files array)
115
+ remove_reference(file)
116
+ # Adds just one back
117
+ self << file
118
+ Synx::Tabber.puts "#{file.basename} (removed duplicate reference)".red
119
+ end
120
+ end
121
+ private :squash_duplicate_file_references
122
+
123
+ end
124
+ end
125
+ end
126
+ end