1# Copyright (c) 2006-2007, The RubyCocoa Project. 2# All Rights Reserved. 3# 4# RubyCocoa is free software, covered under either the Ruby's license or the 5# LGPL. See the COPYRIGHT file for more information. 6 7begin 8 require 'hpricot' 9rescue LoadError 10 require 'rubygems' 11 require 'hpricot' 12end 13require 'osx/cocoa' 14 15lib_path = File.join(File.dirname(File.expand_path(__FILE__)), 'lib/') 16require "#{lib_path}/extras" 17require "#{lib_path}/class_additions" 18require "#{lib_path}/clean_up" 19require "#{lib_path}/hpricot_proxy" 20require "#{lib_path}/class_def" 21require "#{lib_path}/method_def" 22require "#{lib_path}/constant_def" 23require "#{lib_path}/notification_def" 24require "#{lib_path}/function_def" 25require "#{lib_path}/datatype_def" 26 27module CocoaRef 28 class Log 29 def initialize 30 @errors = [] 31 end 32 33 def add(str) 34 @errors.push(str) 35 if $COCOA_REF_DEBUG 36 puts str 37 end 38 end 39 40 def errors? 41 not @errors.empty? 42 end 43 end 44 45 class Parser 46 attr_reader :class_def 47 48 def initialize(file, framework = '') 49 @framework = framework.gsub(/Frame\w+/, '') 50 unless framework == 'ApplicationKit' or framework == 'Foundation' or framework.empty? 51 OSX.require_framework @framework 52 end 53 54 @log = CocoaRef::Log.new 55 56 # LOAD THE HPRICOT PROXY AND ANY OVERRIDES IF NEEDED 57 @hpricot = CocoaRef::HpricotProxy.new(file) 58 html_parser_overrides_file = File.join(File.dirname(File.expand_path(__FILE__)), @framework, 'HTMLParserOverrides.rb') 59 if File.exist?(html_parser_overrides_file) 60 # If it exists, require it and extend the html parser to use the overrides 61 require html_parser_overrides_file 62 @hpricot.extend HTMLParserOverrides 63 end 64 65 # START PARSING 66 @class_def = parse_reference(file) 67 @class_def.framework = @framework 68 69 # Check if there is a overrides file in the override_dir for the given class 70 class_overrides_file = File.join(File.dirname(File.expand_path(__FILE__)), @framework, @class_def.output_filename) 71 if File.exist?(class_overrides_file) 72 # If it exists, require it and extend the methods to use the overrides 73 require class_overrides_file 74 @class_def.method_defs.each do |m| 75 m.instance_eval "self.send :extend, #{@class_def.name}Overrides" 76 end 77 @class_def.delegate_method_defs.each do |dm| 78 dm.instance_eval "self.send :extend, #{@class_def.name}Overrides" 79 end 80 @class_def.function_defs.each do |f| 81 f.instance_eval "self.send :extend, #{@class_def.name}FunctionsOverrides" 82 end 83 end 84 end 85 86 def empty? 87 @class_def.empty? 88 end 89 90 def errors? 91 @log.errors? or @hpricot.log.errors? or @class_def.errors? 92 end 93 94 def parse_reference(file) 95 class_def = ClassDef.new 96 busy_with = '' 97 constant_type = :normal 98 index = 0 99 @hpricot.elements.each do |element| 100 if element.fits_the_description?('h1', 'Class Reference') or element.fits_the_description?('h1', 'Class Objective-C Reference') 101 class_def.type = :class 102 class_def.name = element.inner_html.strip_tags.split(' ').first 103 elsif element.fits_the_description?('h1', 'Additions Reference') 104 class_def.type = :additions 105 class_def.name = element.inner_html.strip_tags.split(' ').first 106 elsif element.fits_the_description?('h1', 'Deprecation Reference') 107 # FIXME: this is a check for NSObjCTypeSerializationCallBack. 108 # It's deprecated therefor it shows up here, but it's a protocol ref actually. 109 # Maybe we need to skip deprecated refs anyway... 110 name = element.inner_html.strip_tags.split(' ').first 111 if name == 'NSObjCTypeSerializationCallBack' 112 class_def.type = :protocols 113 class_def.name = name 114 else 115 class_def.type = :class 116 class_def.name = name 117 end 118 elsif element.fits_the_description?('h1', 'Functions Reference') 119 class_def.type = :functions 120 class_def.name = @framework 121 elsif element.fits_the_description?('h1', 'Data Types Reference') 122 class_def.type = :data_types 123 class_def.name = @framework 124 elsif element.fits_the_description?('h1', 'Constants Reference') 125 class_def.type = :constants 126 class_def.name = @framework 127 elsif element.fits_the_description?('h1', 'Protocol') # FIXME: Had to remove the ref part because of a bug with appkit/protocols/NSIgnoreMisspelledWords 128 class_def.type = :protocols 129 class_def.name = element.inner_html.strip_tags.split(' ').first 130 end 131 132 if element.fits_the_description?('h2', 'Class Description') 133 class_def.description = @hpricot.get_the_text(index + 1).first 134 end 135 136 if element.fits_the_description?('h2', 'Class Methods') 137 busy_with = 'Class Methods' 138 end 139 if busy_with == 'Class Methods' and @hpricot.start_of_method_def?(index) 140 class_def.method_defs.push @hpricot.get_method_def(index, :class_method) 141 end 142 143 if element.fits_the_description?('h2', 'Instance Methods') 144 busy_with = 'Instance Methods' 145 end 146 if busy_with == 'Instance Methods' and @hpricot.start_of_method_def?(index) 147 class_def.method_defs.push @hpricot.get_method_def(index, :instance_method) 148 end 149 150 if element.fits_the_description?('h2', 'Delegate Methods') 151 busy_with = 'Delegate Methods' 152 end 153 if busy_with == 'Delegate Methods' and @hpricot.start_of_method_def?(index) 154 class_def.delegate_method_defs.push @hpricot.get_method_def(index, :instance_method) 155 end 156 157 if class_def.type == :functions and element.fits_the_description?('h2', 'Functions') 158 busy_with = 'Functions' 159 end 160 if busy_with == 'Functions' and @hpricot.start_of_method_def?(index) 161 class_def.function_defs.push @hpricot.get_methodlike_function_def(index) 162 end 163 164 if class_def.type == :data_types and element.fits_the_description?('h2', 'Data Types') 165 busy_with = 'Data Types' 166 end 167 if busy_with == 'Data Types' and @hpricot.start_of_method_def?(index) 168 datatype = @hpricot.get_methodlike_datatype_def(index) 169 class_def.datatype_defs.push datatype unless datatype.nil? 170 end 171 172 if element.fits_the_description?('h2', 'Notifications') 173 busy_with = 'Notifications' 174 end 175 if busy_with == 'Notifications' and @hpricot.start_of_method_def?(index) 176 class_def.notification_defs.push @hpricot.get_notification_def(index) 177 end 178 179 if element.fits_the_description?('h2', 'Constants') 180 busy_with = 'Constants' 181 end 182 if busy_with == 'Constants' and class_def.type != :constants and @hpricot.start_of_method_def?(index) 183 constants = @hpricot.get_constant_defs(index) 184 class_def.constant_defs.concat(constants) unless constants.nil? 185 end 186 # There are also constant descriptions which are more like method descriptions 187 if busy_with == 'Constants' and class_def.type != :constants and @hpricot.start_of_method_def?(index) 188 constant = @hpricot.get_methodlike_constant_def(index) 189 if not constant.discussion.empty? and not constant.availability.empty? 190 class_def.constant_defs.push constant 191 end 192 end 193 194 # For the constants in the misc dir there are several types of constants, 195 # we probably want to use this as different sections. 196 if busy_with == 'Constants' and class_def.type == :constants 197 if element.fits_the_description?('h3', 'Enumerations') 198 constant_type = :enumeration 199 elsif element.fits_the_description?('h3', 'Global Variables') 200 constant_type = :global 201 elsif element.fits_the_description?('h3', 'Errors') 202 constant_type = :error 203 elsif element.fits_the_description?('h3', 'Notifications') 204 constant_type = :notification 205 elsif element.fits_the_description?('h3', 'Exceptions') 206 constant_type = :exception 207 end 208 end 209 # Then there are the constants defined in the misc dir, which are a bit different 210 if busy_with == 'Constants' and class_def.type == :constants and @hpricot.start_of_framework_constant_def?(index) 211 constants = @hpricot.get_constant_defs(index, constant_type) 212 class_def.constant_defs.concat(constants) unless constants.nil? 213 elsif busy_with == 'Constants' and class_def.type == :constants and @hpricot.start_of_method_def?(index) 214 constants = @hpricot.get_constant_defs(index) 215 class_def.constant_defs.concat(constants) unless constants.nil? 216 end 217 218 index = index.next 219 end 220 221 # Check if we got a name for the class 222 if class_def.name.empty? 223 error_str = "[ERROR] A empty string was returned as the class name\n" 224 @log.add(error_str) 225 end 226 227 # Remove duplicate constants 228 class_def.constant_defs.uniq! 229 230 class_def.create_syntactic_sugar_methods! 231 232 return class_def 233 end 234 235 def to_rb_file(dir) 236 file = File.join(dir, @class_def.output_filename) 237 File.open(file, 'w') {|f| f.write @class_def.to_rdoc} 238 end 239 240 end 241end 242