xdry 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +11 -0
  3. data/Rakefile +76 -0
  4. data/VERSION +1 -0
  5. data/bin/xdry +4 -0
  6. data/lib/xdry.rb +18 -0
  7. data/lib/xdry/boxing.rb +212 -0
  8. data/lib/xdry/generators/ctor_from_field.rb +91 -0
  9. data/lib/xdry/generators/dealloc.rb +53 -0
  10. data/lib/xdry/generators/dictionary_coding.rb +129 -0
  11. data/lib/xdry/generators/field_from_property.rb +20 -0
  12. data/lib/xdry/generators/property-from-field.rb +22 -0
  13. data/lib/xdry/generators/storing_constructor.rb +72 -0
  14. data/lib/xdry/generators/synthesize.rb +25 -0
  15. data/lib/xdry/generators_support.rb +42 -0
  16. data/lib/xdry/parsing/driver.rb +106 -0
  17. data/lib/xdry/parsing/model.rb +272 -0
  18. data/lib/xdry/parsing/nodes.rb +260 -0
  19. data/lib/xdry/parsing/parsers.rb +166 -0
  20. data/lib/xdry/parsing/parts/selectors.rb +95 -0
  21. data/lib/xdry/parsing/parts/var_types.rb +66 -0
  22. data/lib/xdry/parsing/pos.rb +75 -0
  23. data/lib/xdry/parsing/scope_stack.rb +68 -0
  24. data/lib/xdry/parsing/scopes.rb +61 -0
  25. data/lib/xdry/parsing/scopes_support.rb +143 -0
  26. data/lib/xdry/patching/emitter.rb +60 -0
  27. data/lib/xdry/patching/insertion_points.rb +209 -0
  28. data/lib/xdry/patching/item_patchers.rb +74 -0
  29. data/lib/xdry/patching/patcher.rb +139 -0
  30. data/lib/xdry/run.rb +227 -0
  31. data/lib/xdry/support/enumerable_additions.rb +35 -0
  32. data/lib/xdry/support/string_additions.rb +27 -0
  33. data/lib/xdry/support/symbol_additions.rb +14 -0
  34. data/site/_config.yml +3 -0
  35. data/site/_example +9 -0
  36. data/site/_layouts/default.html +30 -0
  37. data/site/_plugins/example.rb +16 -0
  38. data/site/_plugins/highlight_unindent.rb +17 -0
  39. data/site/index.md +417 -0
  40. data/site/master.css +94 -0
  41. data/spec/boxing_spec.rb +80 -0
  42. data/spec/ctor_from_field_spec.rb +251 -0
  43. data/spec/dealloc_spec.rb +103 -0
  44. data/spec/dictionary_coding_spec.rb +132 -0
  45. data/spec/field_from_prop_spec.rb +72 -0
  46. data/spec/prop_from_field_spec.rb +39 -0
  47. data/spec/readme_samples_spec.rb +76 -0
  48. data/spec/spec.opts +3 -0
  49. data/spec/spec_helper.rb +53 -0
  50. data/spec/synthesize_spec.rb +94 -0
  51. data/xdry.gemspec +103 -0
  52. metadata +141 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011, Andrey Tarantsov <andreyvit@gmail.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,11 @@
1
+ Extra DRY: Objective-C boilerplate generator
2
+ ============================================
3
+
4
+ Automatically adds:
5
+
6
+ * `@property`, `@synthesize`, field declarations,
7
+ * initializing constructors,
8
+ * missing release calls in dealloc,
9
+ * initWithDictionary, dictionaryRepresentation based on the chosen fields.
10
+
11
+ See http://andreyvit.github.com/xdry/ for usage and installation instructions.
data/Rakefile ADDED
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "xdry"
8
+ gem.summary = %Q{eXtra D.R.Y. for Xcode}
9
+ gem.description = %Q{
10
+ Autogenerates all kinds of funky stuff (like accessors) in Xcode projects
11
+ }.strip
12
+ gem.email = "andreyvit@gmail.com"
13
+ gem.homepage = "http://github.com/mockko/xdry"
14
+ gem.authors = ["Andrey Tarantsov"]
15
+ gem.add_development_dependency "rspec", ">= 1.2.9"
16
+ # http://www.rubygems.org/read/chapter/20
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rspec/core'
24
+ require 'rspec/core/rake_task'
25
+ RSpec::Core::RakeTask.new(:spec) do |spec|
26
+ spec.pattern = FileList['spec/**/*_spec.rb']
27
+ end
28
+
29
+ task :default => :spec
30
+
31
+ namespace :site do
32
+ desc "Regenerate the site using Jekyll"
33
+ task :build do
34
+ Dir.chdir 'site' do
35
+ system "jekyll"
36
+ end
37
+ end
38
+
39
+ desc "Open a generated site in a web browser"
40
+ task :open => [:build] do
41
+ system "open site/_site/index.html"
42
+ end
43
+
44
+ desc "Regenerate the site and update gh-pages branch"
45
+ task :update => [:build] do
46
+ mods = `git status --porcelain --untracked-files=no`
47
+ if mods.strip.size > 0
48
+ system "git status --short --untracked-files=no"
49
+ puts
50
+ puts "** There are uncommitted changes. Please commit or stash them before updating the site."
51
+ exit
52
+ end
53
+
54
+ source_commit = `git rev-parse HEAD`.strip
55
+
56
+ ENV['GIT_INDEX_FILE'] = File.expand_path('.git/index-gh-pages')
57
+ ENV['GIT_WORK_TREE'] = File.expand_path('site/_site')
58
+ ENV['GIT_DIR'] = File.expand_path('.git')
59
+ Dir.chdir 'site/_site' do
60
+ system "git update-index --add --remove #{Dir["**/*"].join(' ')}"
61
+ tree=`git write-tree`.strip
62
+ puts "Tree = #{tree}"
63
+ parent=`git rev-parse refs/heads/gh-pages`.strip
64
+ puts "Parent = #{parent}"
65
+ commit=`echo "Generate the site from source repository commit #{source_commit}" | git commit-tree #{tree} -p #{parent}`.strip
66
+ puts "Commit = #{commit}"
67
+ system "git update-ref -m 'Generate the site from source repository commit #{source_commit}' refs/heads/gh-pages #{commit}"
68
+ end
69
+ end
70
+
71
+ desc "Push an updated site to GitHub"
72
+ task :push do
73
+ system "git push origin gh-pages"
74
+ end
75
+
76
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/xdry ADDED
@@ -0,0 +1,4 @@
1
+ #! /usr/bin/env ruby
2
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'xdry'
4
+ XDry.run(ARGV)
data/lib/xdry.rb ADDED
@@ -0,0 +1,18 @@
1
+
2
+ module XDry
3
+ INDENT_STEP = "\t" # FIXME: all usages of this should be changed to something smart
4
+ FIELD_PREFIX = "_" # FIXME: all usages of this should be changed to something smart
5
+ end
6
+
7
+ %w{
8
+ support/string_additions support/symbol_additions support/enumerable_additions
9
+ parsing/parts/var_types parsing/parts/selectors
10
+ parsing/nodes parsing/model parsing/pos parsing/parsers
11
+ parsing/scopes_support parsing/scopes
12
+ parsing/scope_stack parsing/driver
13
+
14
+ patching/emitter patching/patcher patching/insertion_points patching/item_patchers
15
+
16
+ boxing generators_support run
17
+ }.each { |name| require File.join(File.dirname(__FILE__), 'xdry', name) }
18
+ Dir[File.join(File.dirname(__FILE__), 'xdry', 'generators', '*.rb')].each { |f| require f }
@@ -0,0 +1,212 @@
1
+ module XDry
2
+
3
+ module RetainPolicy
4
+
5
+ module ASSIGN_VALUE
6
+ def self.retain expr
7
+ expr
8
+ end
9
+
10
+ def self.release out, expr
11
+ end
12
+
13
+ def self.release_and_clear out, var_name
14
+ end
15
+ end
16
+
17
+ module ASSIGN_REF
18
+ def self.retain expr
19
+ expr
20
+ end
21
+
22
+ def self.release out, expr
23
+ end
24
+
25
+ def self.release_and_clear out, var_name
26
+ out << "#{var_name} = nil;";
27
+ end
28
+ end
29
+
30
+ module COPY
31
+ def self.retain expr
32
+ "[#{expr} copy]"
33
+ end
34
+
35
+ def self.release out, expr
36
+ out << "[#{expr} release]";
37
+ end
38
+
39
+ def self.release_and_clear out, var_name
40
+ out << "[#{var_name} release], #{var_name} = nil;";
41
+ end
42
+ end
43
+
44
+ module RETAIN
45
+ def self.retain expr
46
+ "[#{expr} retain]"
47
+ end
48
+
49
+ def self.release out, expr
50
+ out << "[#{expr} release]";
51
+ end
52
+
53
+ def self.release_and_clear out, var_name
54
+ out << "[#{var_name} release], #{var_name} = nil;";
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ module Boxing
61
+
62
+ class Boxer
63
+ def unbox_retained out, object_expr, tempvar_prefix
64
+ retain_policy.retain(unbox(out, object_expr, tempvar_prefix))
65
+ end
66
+ end
67
+
68
+ class NSNumberConverter < Boxer
69
+ def initialize repr_selector, init_selector
70
+ @init_selector = init_selector
71
+ @repr_selector = repr_selector
72
+ end
73
+
74
+ def box out, data_expr, tempvar_prefix
75
+ "[NSNumber #{@init_selector}:#{data_expr}]"
76
+ end
77
+
78
+ def unbox out, object_expr, tempvar_prefix
79
+ "[#{object_expr} #{@repr_selector}]"
80
+ end
81
+
82
+ def retain_policy; RetainPolicy::ASSIGN_VALUE; end
83
+ end
84
+
85
+ class DateConverter < Boxer
86
+ def box out, data_expr, tempvar_prefix
87
+ SIMPLE_CONVERTIONS['double'].box out, "[#{data_expr} timeIntervalSinceReferenceDate]"
88
+ end
89
+
90
+ def unbox out, object_expr, tempvar_prefix
91
+ number = SIMPLE_CONVERTIONS['double'].unbox(out, object_expr)
92
+ "[NSDate dateWithTimeIntervalSinceReferenceDate:#{number}]"
93
+ end
94
+
95
+ def retain_policy; RetainPolicy::RETAIN; end
96
+ end
97
+
98
+ class NopConverter < Boxer
99
+ def box out, data_expr, tempvar_prefix
100
+ data_expr
101
+ end
102
+
103
+ def unbox out, object_expr, tempvar_prefix
104
+ object_expr
105
+ end
106
+
107
+ def retain_policy; RetainPolicy::RETAIN; end
108
+ end
109
+
110
+ class StringConverter < Boxer
111
+ def box out, data_expr, tempvar_prefix
112
+ data_expr
113
+ end
114
+
115
+ def unbox out, object_expr, tempvar_prefix
116
+ object_expr
117
+ end
118
+
119
+ def retain_policy; RetainPolicy::COPY; end
120
+ end
121
+
122
+ class ArrayConverter < Boxer
123
+ def initialize item_type, init_selector, repr_selector
124
+ @item_type = item_type
125
+ @init_selector = init_selector
126
+ @repr_selector = repr_selector
127
+ end
128
+
129
+ def box out, data_expr, tempvar_prefix
130
+ array_var = "#{tempvar_prefix}Array"
131
+ item_var = "#{tempvar_prefix}Item"
132
+
133
+ out << "NSMutableArray *#{array_var} = [NSMutableArray array];"
134
+ out.block "for (#{@item_type} *#{item_var} in #{data_expr})" do
135
+ out << "[#{array_var} addObject:[#{item_var} #{@repr_selector}]];"
136
+ end
137
+ "#{array_var}"
138
+ end
139
+
140
+ def unbox out, object_expr, tempvar_prefix
141
+ unbox_internal out, object_expr, tempvar_prefix, "[NSMutableArray array]"
142
+ end
143
+
144
+ def unbox_retained out, object_expr, tempvar_prefix
145
+ unbox_internal out, object_expr, tempvar_prefix, "[[NSMutableArray alloc] init]"
146
+ end
147
+
148
+ def retain_policy; RetainPolicy::COPY; end
149
+
150
+ private
151
+
152
+ def unbox_internal out, object_expr, tempvar_prefix, array_init
153
+ array_var = "#{tempvar_prefix}Array"
154
+ item_dict_var = "#{tempvar_prefix}ItemDictionary"
155
+ item_var = "#{tempvar_prefix}Item"
156
+
157
+ out << "NSMutableArray *#{array_var} = #{array_init};"
158
+ out.block "for (NSDictionary *#{item_dict_var} in (NSArray *) #{object_expr})" do
159
+ out << "#{@item_type} *#{item_var} = [[#{@item_type} alloc] #{@init_selector}:#{item_dict_var}];"
160
+ out << "[#{array_var} addObject:#{item_var}];"
161
+ out << "[#{item_var} release];"
162
+ end
163
+ "#{array_var}"
164
+ end
165
+ end
166
+
167
+ SIMPLE_CONVERTIONS = {
168
+ 'int' => NSNumberConverter.new('intValue', 'numberWithInt'),
169
+ 'NSInteger' => NSNumberConverter.new('integerValue', 'numberWithInteger'),
170
+ 'NSUInteger' => NSNumberConverter.new('unsignedIntegerValue', 'numberWithUnsignedInteger'),
171
+ 'BOOL' => NSNumberConverter.new('boolValue', 'numberWithBool'),
172
+ 'float' => NSNumberConverter.new('floatValue', 'numberWithFloat'),
173
+ 'CGFloat' => NSNumberConverter.new('floatValue', 'numberWithFloat'),
174
+ 'double' => NSNumberConverter.new('doubleValue', 'numberWithDouble'),
175
+ 'NSTimeInterval' => NSNumberConverter.new('doubleValue', 'numberWithDouble'),
176
+ }
177
+
178
+ POINTER_CONVERTIONS = {
179
+ 'NSDate' => DateConverter.new,
180
+ 'NSString' => StringConverter.new,
181
+ }
182
+
183
+ def self.converter_for type
184
+ case type
185
+ when SimpleVarType
186
+ SIMPLE_CONVERTIONS[type.name]
187
+ when PointerVarType
188
+ case type.name
189
+ when 'NSArray', 'NSMutableArray'
190
+ return ArrayConverter.new(type.type_hint || 'NSObject', 'initWithDictionary', 'dictionaryRepresentation')
191
+ end
192
+ POINTER_CONVERTIONS[type.name]
193
+ else
194
+ nil
195
+ end
196
+ end
197
+
198
+ def self.retain_policy_of type
199
+ if conv = converter_for(type)
200
+ conv.retain_policy
201
+ else
202
+ case type
203
+ when PointerVarType then RetainPolicy::RETAIN
204
+ when IdVarType then RetainPolicy::ASSIGN_REF
205
+ when SimpleVarType then RetainPolicy::ASSIGN_VALUE
206
+ end
207
+ end
208
+ end
209
+
210
+ end
211
+
212
+ end
@@ -0,0 +1,91 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ class ConstructorFromField < Generator
6
+ id "ctor-from-field"
7
+
8
+ def process_class oclass
9
+ attributes = oclass.attributes.select { |a| a.wants_constructor? }
10
+ return if attributes.empty?
11
+
12
+ patch_implementation! oclass, attributes
13
+ patch_interface! oclass, attributes
14
+ end
15
+
16
+ def patch_implementation! oclass, attributes
17
+ impl_selector_def = impl_selector_def_for(attributes)
18
+
19
+ init_code = Emitter.capture do |o|
20
+ o.method "(id)#{impl_selector_def}" do
21
+ o.if "self = [super init]" do
22
+ end
23
+ o << "return self;"
24
+ end
25
+ end
26
+
27
+ MethodPatcher.new(patcher, oclass, impl_selector_def.selector, ImplementationStartIP.new(oclass), init_code) do |omethod|
28
+ impl = omethod.impl
29
+ ip = InsideConstructorIfSuperIP.new(impl)
30
+
31
+ lines = Emitter.capture do |o|
32
+ attributes.zip(impl_selector_def.components).each do |oattr, selector_component|
33
+ name, type = oattr.name, oattr.type
34
+ capitalized_name = name.capitalized_identifier
35
+
36
+ retain_policy = Boxing.retain_policy_of type
37
+
38
+ unless impl.children.any? { |n| NLine === n && n.line =~ /^(?:self\s*.\s*#{oattr.name}|#{oattr.field_name})\s*=/ }
39
+
40
+ var_name = impl.start_node.selector_def.var_name_after_keyword(selector_component.keyword)
41
+
42
+ field_ref = oattr.field_name
43
+ field_ref = "self->#{field_ref}" if field_ref == var_name
44
+
45
+ retained = retain_policy.retain(var_name)
46
+ o << "#{field_ref} = #{retained};"
47
+ end
48
+ end
49
+ end
50
+
51
+ ip.insert @patcher, lines unless lines.empty?
52
+ end
53
+ end
54
+
55
+ def patch_interface! oclass, attributes
56
+ intf_selector_def = intf_selector_def_for(attributes)
57
+
58
+ omethod = oclass.find_method(intf_selector_def.selector)
59
+ unless omethod && omethod.has_header?
60
+
61
+ ip = BeforeInterfaceEndIP.new(oclass)
62
+ lines = Emitter.capture do |o|
63
+ o << "- (id)#{intf_selector_def};"
64
+ end
65
+ ip.insert patcher, [""] + lines + [""]
66
+
67
+ end
68
+ end
69
+
70
+ def impl_selector_def_for attributes
71
+ CompoundSelectorDef.new(attributes.each_with_index.collect { |a, i| selector_component_for(a, i, true) })
72
+ end
73
+
74
+ def intf_selector_def_for attributes
75
+ CompoundSelectorDef.new(attributes.each_with_index.collect { |a, i| selector_component_for(a, i, false) })
76
+ end
77
+
78
+ def selector_component_for attribute, index, is_impl
79
+ keyword = attribute.name
80
+ keyword = "initWith#{keyword.capitalized_identifier}" if index == 0
81
+
82
+ arg_name = attribute.name
83
+ arg_name = arg_name.prefixed_as_arg_name if is_impl && arg_name == attribute.field_name
84
+
85
+ SelectorComponent.new("#{keyword}:", arg_name, attribute.type)
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
@@ -0,0 +1,53 @@
1
+
2
+ module XDry
3
+ module Generators
4
+
5
+ DEALLOC_CODE = [
6
+ "",
7
+ "- (void)dealloc {",
8
+ "\t[super dealloc];",
9
+ "}",
10
+ "",
11
+ ]
12
+
13
+ class Dealloc < Generator
14
+ id "dealloc"
15
+
16
+ def process_class oclass
17
+
18
+ MethodPatcher.new(patcher, oclass, 'dealloc', ImplementationStartIP.new(oclass), DEALLOC_CODE) do |dealloc_method|
19
+ impl = dealloc_method.impl
20
+ ip = BeforeSuperCallIP.new(impl)
21
+
22
+ existing_releases = impl.children.select { |child| child.is_a? NReleaseCall }
23
+ lines = generate_release_calls_if(oclass) do |oattr|
24
+ !existing_releases.any? { |n| n.expr == oattr.field_name || n.expr == "self.#{oattr.name}" }
25
+ end
26
+
27
+ ip.insert @patcher, lines unless lines.empty?
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def generate_release_calls_if oclass
34
+ dealloc_out = Emitter.new
35
+
36
+ oclass.attributes.each do |oattr|
37
+ next if not oattr.has_field_def?
38
+
39
+ field_name = oattr.field_name
40
+ retain_policy = Boxing.retain_policy_of(oattr.type)
41
+
42
+ if yield(oattr)
43
+ retain_policy.release_and_clear dealloc_out, field_name
44
+ end
45
+ end
46
+
47
+ dealloc_out.lines
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end