AXElements 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/.yardopts +4 -0
  2. data/History.markdown +41 -0
  3. data/README.markdown +59 -62
  4. data/Rakefile +1 -1
  5. data/ext/accessibility/key_coder/extconf.rb +1 -1
  6. data/ext/accessibility/key_coder/key_coder.c +8 -5
  7. data/lib/accessibility/dsl.rb +261 -87
  8. data/lib/accessibility/enumerators.rb +14 -11
  9. data/lib/accessibility/errors.rb +4 -3
  10. data/lib/accessibility/factory.rb +159 -108
  11. data/lib/accessibility/graph.rb +13 -9
  12. data/lib/accessibility/{pp_inspector.rb → pretty_printer.rb} +4 -5
  13. data/lib/accessibility/qualifier.rb +23 -13
  14. data/lib/accessibility/string.rb +4 -4
  15. data/lib/accessibility/system_info.rb +230 -0
  16. data/lib/accessibility/translator.rb +38 -28
  17. data/lib/accessibility/version.rb +24 -2
  18. data/lib/accessibility.rb +25 -8
  19. data/lib/ax/application.rb +207 -77
  20. data/lib/ax/element.rb +62 -65
  21. data/lib/ax/menu.rb +5 -1
  22. data/lib/ax/row.rb +1 -1
  23. data/lib/ax/scroll_area.rb +7 -6
  24. data/lib/ax/systemwide.rb +38 -5
  25. data/lib/ax_elements/active_support_selections.rb +10 -0
  26. data/lib/ax_elements/mri.rb +57 -0
  27. data/lib/ax_elements/nsarray_compat.rb +97 -17
  28. data/lib/ax_elements.rb +9 -1
  29. data/rakelib/gem.rake +11 -11
  30. data/rakelib/test.rake +0 -9
  31. data/test/helper.rb +10 -18
  32. data/test/integration/accessibility/test_dsl.rb +52 -42
  33. data/test/integration/accessibility/test_enumerators.rb +0 -1
  34. data/test/integration/accessibility/test_graph.rb +1 -0
  35. data/test/integration/accessibility/test_qualifier.rb +2 -2
  36. data/test/integration/ax/test_application.rb +9 -2
  37. data/test/integration/ax/test_element.rb +41 -1
  38. data/test/sanity/accessibility/test_factory.rb +23 -56
  39. data/test/sanity/accessibility/{test_pp_inspector.rb → test_pretty_printer.rb} +9 -9
  40. data/test/sanity/accessibility/test_translator.rb +2 -5
  41. data/test/sanity/accessibility/test_version.rb +15 -0
  42. data/test/sanity/ax/test_application.rb +17 -2
  43. data/test/sanity/ax/test_element.rb +2 -2
  44. data/test/sanity/ax_elements/test_nsobject_inspect.rb +4 -2
  45. data/test/sanity/test_ax_elements.rb +1 -0
  46. metadata +69 -39
  47. data/lib/accessibility/core.rb +0 -973
  48. data/lib/accessibility/highlighter.rb +0 -86
  49. data/lib/ax_elements/vendor/inflection_data.rb +0 -66
  50. data/lib/ax_elements/vendor/inflections.rb +0 -172
  51. data/lib/ax_elements/vendor/inflector.rb +0 -306
  52. data/lib/minitest/ax_elements.rb +0 -175
  53. data/lib/mouse.rb +0 -223
  54. data/lib/rspec/expectations/ax_elements.rb +0 -234
  55. data/test/integration/accessibility/test_core.rb +0 -18
  56. data/test/integration/minitest/test_ax_elements.rb +0 -89
  57. data/test/integration/rspec/expectations/test_ax_elements.rb +0 -102
  58. data/test/sanity/accessibility/test_core.rb +0 -561
  59. data/test/sanity/accessibility/test_highlighter.rb +0 -56
  60. data/test/sanity/minitest/test_ax_elements.rb +0 -17
  61. data/test/sanity/rspec/expectations/test_ax_elements.rb +0 -15
  62. data/test/sanity/test_mouse.rb +0 -19
@@ -1,86 +0,0 @@
1
- framework 'AppKit'
2
- require 'accessibility/version'
3
-
4
- ##
5
- # A screen highlighter for debugging. When you initialize a highligter
6
- # object it will highlight the given bounds on the screen.
7
- #
8
- # Highligter objects can have their colour configured at initialization,
9
- # and can also have a timeout to automatically stop displaying.
10
- #
11
- # @example
12
- #
13
- # h = Accessibility::Highlighter.new(CGRectMake(100,100,100,100))
14
- # # when you are done...
15
- # h.stop
16
- #
17
- class Accessibility::Highlighter < NSWindow
18
-
19
- # @param [CGRect]
20
- # @param [Hash] opts
21
- # @option opts [Number] :timeout
22
- # @option opts [NSColor] :colour (NSColor.magentaColor)
23
- def initialize bounds, opts = {}
24
- colour = opts[:colour] || opts[:color] || NSColor.magentaColor
25
-
26
- bounds.flip! # we assume the rect is in the other co-ordinate system
27
-
28
- initWithContentRect bounds,
29
- styleMask: NSBorderlessWindowMask,
30
- backing: NSBackingStoreBuffered,
31
- defer: true
32
- setOpaque false
33
- setAlphaValue 0.20
34
- setLevel NSStatusWindowLevel
35
- setBackgroundColor colour
36
- setIgnoresMouseEvents true
37
- setFrame bounds, display: false
38
- makeKeyAndOrderFront NSApp
39
-
40
- if opts.has_key? :timeout
41
- Dispatch::Queue.new(queue_id).after opts[:timeout] do
42
- self.stop
43
- end
44
- end
45
- end
46
-
47
- ##
48
- # Tell the highlighter to stop displaying.
49
- #
50
- # @return [self]
51
- def stop
52
- close
53
- end
54
-
55
-
56
- private
57
-
58
- def queue_id
59
- "com.marketcircle.axelements.window_killer_#{hash}"
60
- end
61
- end
62
-
63
-
64
- ##
65
- # AXElements extensions to `CGRect`.
66
- class CGRect
67
- ##
68
- # Treats the rect as belonging to one co-ordinate system and then
69
- # converts it to the other system.
70
- #
71
- # This is useful because accessibility API's expect to work with
72
- # the flipped co-ordinate system (origin in top left), but AppKit
73
- # prefers to use the cartesian co-ordinate system (origin in bottom
74
- # left).
75
- #
76
- # @return [CGRect]
77
- def flip!
78
- screen_height = NSMaxY(NSScreen.mainScreen.frame)
79
- origin.y = screen_height - NSMaxY(self)
80
- self
81
- end
82
- end
83
-
84
-
85
- # Initialize the shared application so that windows can be created
86
- NSApplication.sharedApplication
@@ -1,66 +0,0 @@
1
- module Accessibility
2
- Inflector.inflections do |inflect|
3
- inflect.plural(/$/, 's')
4
- inflect.plural(/s$/i, 's')
5
- inflect.plural(/(ax|test)is$/i, '\1es')
6
- inflect.plural(/(octop|vir)us$/i, '\1i')
7
- inflect.plural(/(octop|vir)i$/i, '\1i')
8
- inflect.plural(/(alias|status)$/i, '\1es')
9
- inflect.plural(/(bu)s$/i, '\1ses')
10
- inflect.plural(/(buffal|tomat)o$/i, '\1oes')
11
- inflect.plural(/([ti])um$/i, '\1a')
12
- inflect.plural(/([ti])a$/i, '\1a')
13
- inflect.plural(/sis$/i, 'ses')
14
- inflect.plural(/(?:([^f])fe|([lr])f)$/i, '\1\2ves')
15
- inflect.plural(/(hive)$/i, '\1s')
16
- inflect.plural(/([^aeiouy]|qu)y$/i, '\1ies')
17
- inflect.plural(/(x|ch|ss|sh)$/i, '\1es')
18
- inflect.plural(/(matr|vert|ind)(?:ix|ex)$/i, '\1ices')
19
- inflect.plural(/(m|l)ouse$/i, '\1ice')
20
- inflect.plural(/(m|l)ice$/i, '\1ice')
21
- inflect.plural(/^(ox)$/i, '\1en')
22
- inflect.plural(/^(oxen)$/i, '\1')
23
- inflect.plural(/(quiz)$/i, '\1zes')
24
-
25
- inflect.singular(/s$/i, '')
26
- inflect.singular(/(n)ews$/i, '\1ews')
27
- inflect.singular(/([ti])a$/i, '\1um')
28
- inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '\1\2sis')
29
- inflect.singular(/(^analy)ses$/i, '\1sis')
30
- inflect.singular(/([^f])ves$/i, '\1fe')
31
- inflect.singular(/(hive)s$/i, '\1')
32
- inflect.singular(/(tive)s$/i, '\1')
33
- inflect.singular(/([lr])ves$/i, '\1f')
34
- inflect.singular(/([^aeiouy]|qu)ies$/i, '\1y')
35
- inflect.singular(/(s)eries$/i, '\1eries')
36
- inflect.singular(/(m)ovies$/i, '\1ovie')
37
- inflect.singular(/(x|ch|ss|sh)es$/i, '\1')
38
- inflect.singular(/(m|l)ice$/i, '\1ouse')
39
- inflect.singular(/(bus)es$/i, '\1')
40
- inflect.singular(/(o)es$/i, '\1')
41
- inflect.singular(/(shoe)s$/i, '\1')
42
- inflect.singular(/(cris|ax|test)es$/i, '\1is')
43
- inflect.singular(/(octop|vir)i$/i, '\1us')
44
- inflect.singular(/(alias|status)es$/i, '\1')
45
- inflect.singular(/^(ox)en/i, '\1')
46
- inflect.singular(/(vert|ind)ices$/i, '\1ex')
47
- inflect.singular(/(matr)ices$/i, '\1ix')
48
- inflect.singular(/(quiz)zes$/i, '\1')
49
- inflect.singular(/(database)s$/i, '\1')
50
-
51
- inflect.irregular('person', 'people')
52
- inflect.irregular('man', 'men')
53
- inflect.irregular('child', 'children')
54
- inflect.irregular('sex', 'sexes')
55
- inflect.irregular('move', 'moves')
56
- inflect.irregular('cow', 'kine')
57
- inflect.irregular('zombie', 'zombies')
58
-
59
- inflect.uncountable(%w(equipment information rice money species series fish sheep jeans))
60
-
61
- # Related to accessibility
62
- inflect.acronym('UI')
63
- inflect.acronym('RTF')
64
- inflect.acronym('URL')
65
- end
66
- end
@@ -1,172 +0,0 @@
1
- module Accessibility
2
- module Inflector
3
- # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
4
- # inflection rules. Examples:
5
- #
6
- # ActiveSupport::Inflector.inflections do |inflect|
7
- # inflect.plural /^(ox)$/i, '\1\2en'
8
- # inflect.singular /^(ox)en/i, '\1'
9
- #
10
- # inflect.irregular 'octopus', 'octopi'
11
- #
12
- # inflect.uncountable "equipment"
13
- # end
14
- #
15
- # New rules are added at the top. So in the example above, the irregular rule for octopus will now be the first of the
16
- # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
17
- # already have been loaded.
18
- class Inflections
19
- def self.instance
20
- @__instance__ ||= new
21
- end
22
-
23
- attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
24
-
25
- def initialize
26
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
27
- end
28
-
29
- # Specifies a new acronym. An acronym must be specified as it will appear in a camelized string. An underscore
30
- # string that contains the acronym will retain the acronym when passed to `camelize`, `humanize`, or `titleize`.
31
- # A camelized string that contains the acronym will maintain the acronym when titleized or humanized, and will
32
- # convert the acronym into a non-delimited single lowercase word when passed to +underscore+.
33
- #
34
- # Examples:
35
- # acronym 'HTML'
36
- # titleize 'html' #=> 'HTML'
37
- # camelize 'html' #=> 'HTML'
38
- # underscore 'MyHTML' #=> 'my_html'
39
- #
40
- # The acronym, however, must occur as a delimited unit and not be part of another word for conversions to recognize it:
41
- #
42
- # acronym 'HTTP'
43
- # camelize 'my_http_delimited' #=> 'MyHTTPDelimited'
44
- # camelize 'https' #=> 'Https', not 'HTTPs'
45
- # underscore 'HTTPS' #=> 'http_s', not 'https'
46
- #
47
- # acronym 'HTTPS'
48
- # camelize 'https' #=> 'HTTPS'
49
- # underscore 'HTTPS' #=> 'https'
50
- #
51
- # Note: Acronyms that are passed to `pluralize` will no longer be recognized, since the acronym will not occur as
52
- # a delimited unit in the pluralized result. To work around this, you must specify the pluralized form as an
53
- # acronym as well:
54
- #
55
- # acronym 'API'
56
- # camelize(pluralize('api')) #=> 'Apis'
57
- #
58
- # acronym 'APIs'
59
- # camelize(pluralize('api')) #=> 'APIs'
60
- #
61
- # `acronym` may be used to specify any word that contains an acronym or otherwise needs to maintain a non-standard
62
- # capitalization. The only restriction is that the word must begin with a capital letter.
63
- #
64
- # Examples:
65
- # acronym 'RESTful'
66
- # underscore 'RESTful' #=> 'restful'
67
- # underscore 'RESTfulController' #=> 'restful_controller'
68
- # titleize 'RESTfulController' #=> 'RESTful Controller'
69
- # camelize 'restful' #=> 'RESTful'
70
- # camelize 'restful_controller' #=> 'RESTfulController'
71
- #
72
- # acronym 'McDonald'
73
- # underscore 'McDonald' #=> 'mcdonald'
74
- # camelize 'mcdonald' #=> 'McDonald'
75
- def acronym(word)
76
- @acronyms[word.downcase] = word
77
- @acronym_regex = /#{@acronyms.values.join("|")}/
78
- end
79
-
80
- # Specifies a new pluralization rule and its replacement. The rule can either be a string or a regular expression.
81
- # The replacement should always be a string that may include references to the matched data from the rule.
82
- def plural(rule, replacement)
83
- @uncountables.delete(rule) if rule.is_a?(String)
84
- @uncountables.delete(replacement)
85
- @plurals.insert(0, [rule, replacement])
86
- end
87
-
88
- # Specifies a new singularization rule and its replacement. The rule can either be a string or a regular expression.
89
- # The replacement should always be a string that may include references to the matched data from the rule.
90
- def singular(rule, replacement)
91
- @uncountables.delete(rule) if rule.is_a?(String)
92
- @uncountables.delete(replacement)
93
- @singulars.insert(0, [rule, replacement])
94
- end
95
-
96
- # Specifies a new irregular that applies to both pluralization and singularization at the same time. This can only be used
97
- # for strings, not regular expressions. You simply pass the irregular in singular and plural form.
98
- #
99
- # Examples:
100
- # irregular 'octopus', 'octopi'
101
- # irregular 'person', 'people'
102
- def irregular(singular, plural)
103
- @uncountables.delete(singular)
104
- @uncountables.delete(plural)
105
- if singular[0,1].upcase == plural[0,1].upcase
106
- plural(Regexp.new("(#{singular[0,1]})#{singular[1..-1]}$", "i"), '\1' + plural[1..-1])
107
- plural(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + plural[1..-1])
108
- singular(Regexp.new("(#{plural[0,1]})#{plural[1..-1]}$", "i"), '\1' + singular[1..-1])
109
- else
110
- plural(Regexp.new("#{singular[0,1].upcase}(?i)#{singular[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
111
- plural(Regexp.new("#{singular[0,1].downcase}(?i)#{singular[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
112
- plural(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), plural[0,1].upcase + plural[1..-1])
113
- plural(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), plural[0,1].downcase + plural[1..-1])
114
- singular(Regexp.new("#{plural[0,1].upcase}(?i)#{plural[1..-1]}$"), singular[0,1].upcase + singular[1..-1])
115
- singular(Regexp.new("#{plural[0,1].downcase}(?i)#{plural[1..-1]}$"), singular[0,1].downcase + singular[1..-1])
116
- end
117
- end
118
-
119
- # Add uncountable words that shouldn't be attempted inflected.
120
- #
121
- # Examples:
122
- # uncountable "money"
123
- # uncountable "money", "information"
124
- # uncountable %w( money information rice )
125
- def uncountable(*words)
126
- (@uncountables << words).flatten!
127
- end
128
-
129
- # Specifies a humanized form of a string by a regular expression rule or by a string mapping.
130
- # When using a regular expression based replacement, the normal humanize formatting is called after the replacement.
131
- # When a string is used, the human form should be specified as desired (example: 'The name', not 'the_name')
132
- #
133
- # Examples:
134
- # human /_cnt$/i, '\1_count'
135
- # human "legacy_col_person_name", "Name"
136
- def human(rule, replacement)
137
- @humans.insert(0, [rule, replacement])
138
- end
139
-
140
- # Clears the loaded inflections within a given scope (default is <tt>:all</tt>).
141
- # Give the scope as a symbol of the inflection type, the options are: <tt>:plurals</tt>,
142
- # <tt>:singulars</tt>, <tt>:uncountables</tt>, <tt>:humans</tt>.
143
- #
144
- # Examples:
145
- # clear :all
146
- # clear :plurals
147
- def clear(scope = :all)
148
- case scope
149
- when :all
150
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
151
- else
152
- instance_variable_set "@#{scope}", []
153
- end
154
- end
155
- end
156
-
157
- # Yields a singleton instance of Inflector::Inflections so you can specify additional
158
- # inflector rules.
159
- #
160
- # Example:
161
- # ActiveSupport::Inflector.inflections do |inflect|
162
- # inflect.uncountable "rails"
163
- # end
164
- def inflections
165
- if block_given?
166
- yield Inflections.instance
167
- else
168
- Inflections.instance
169
- end
170
- end
171
- end
172
- end
@@ -1,306 +0,0 @@
1
- module Accessibility
2
- ##
3
- # The Inflector is code borrowed from Active Support.
4
- #
5
- # The Inflector transforms words from singular to plural, class names to table names, modularized class names to ones without,
6
- # and class names to foreign keys. The default inflections for pluralization, singularization, and uncountable words are kept
7
- # in inflections.rb.
8
- #
9
- # The Rails core team has stated patches for the inflections library will not be accepted
10
- # in order to avoid breaking legacy applications which may be relying on errant inflections.
11
- # If you discover an incorrect inflection and require it for your application, you'll need
12
- # to correct it yourself (explained below).
13
- module Inflector
14
- extend self
15
-
16
- # Returns the plural form of the word in the string.
17
- #
18
- # Examples:
19
- # "post".pluralize # => "posts"
20
- # "octopus".pluralize # => "octopi"
21
- # "sheep".pluralize # => "sheep"
22
- # "words".pluralize # => "words"
23
- # "CamelOctopus".pluralize # => "CamelOctopi"
24
- def pluralize(word)
25
- apply_inflections(word, inflections.plurals)
26
- end
27
-
28
- # The reverse of +pluralize+, returns the singular form of a word in a string.
29
- #
30
- # Examples:
31
- # "posts".singularize # => "post"
32
- # "octopi".singularize # => "octopus"
33
- # "sheep".singularize # => "sheep"
34
- # "word".singularize # => "word"
35
- # "CamelOctopi".singularize # => "CamelOctopus"
36
- def singularize(word)
37
- apply_inflections(word, inflections.singulars)
38
- end
39
-
40
- # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
41
- # is set to <tt>:lower</tt> then +camelize+ produces lowerCamelCase.
42
- #
43
- # +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
44
- #
45
- # Examples:
46
- # "active_record".camelize # => "ActiveRecord"
47
- # "active_record".camelize(:lower) # => "activeRecord"
48
- # "active_record/errors".camelize # => "ActiveRecord::Errors"
49
- # "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
50
- #
51
- # As a rule of thumb you can think of +camelize+ as the inverse of +underscore+,
52
- # though there are cases where that does not hold:
53
- #
54
- # "SSLError".underscore.camelize # => "SslError"
55
- def camelize(term, uppercase_first_letter = true)
56
- string = term.to_s
57
- if uppercase_first_letter
58
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
59
- else
60
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
61
- end
62
- string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::')
63
- end
64
-
65
- # Makes an underscored, lowercase form from the expression in the string.
66
- #
67
- # Changes '::' to '/' to convert namespaces to paths.
68
- #
69
- # Examples:
70
- # "ActiveRecord".underscore # => "active_record"
71
- # "ActiveRecord::Errors".underscore # => active_record/errors
72
- #
73
- # As a rule of thumb you can think of +underscore+ as the inverse of +camelize+,
74
- # though there are cases where that does not hold:
75
- #
76
- # "SSLError".underscore.camelize # => "SslError"
77
- def underscore(camel_cased_word)
78
- word = camel_cased_word.to_s.dup
79
- word.gsub!(/::/, '/')
80
- word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" }
81
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
82
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
83
- word.tr!("-", "_")
84
- word.downcase!
85
- word
86
- end
87
-
88
- # Capitalizes the first word and turns underscores into spaces and strips a
89
- # trailing "_id", if any. Like +titleize+, this is meant for creating pretty output.
90
- #
91
- # Examples:
92
- # "employee_salary" # => "Employee salary"
93
- # "author_id" # => "Author"
94
- def humanize(lower_case_and_underscored_word)
95
- result = lower_case_and_underscored_word.to_s.dup
96
- inflections.humans.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
97
- result.gsub!(/_id$/, "")
98
- result.gsub(/(_)?([a-z\d]*)/i) { "#{$1 && ' '}#{inflections.acronyms[$2] || $2.downcase}" }.gsub(/^\w/) { $&.upcase }
99
- end
100
-
101
- # Capitalizes all the words and replaces some characters in the string to create
102
- # a nicer looking title. +titleize+ is meant for creating pretty output. It is not
103
- # used in the Rails internals.
104
- #
105
- # +titleize+ is also aliased as as +titlecase+.
106
- #
107
- # Examples:
108
- # "man from the boondocks".titleize # => "Man From The Boondocks"
109
- # "x-men: the last stand".titleize # => "X Men: The Last Stand"
110
- # "TheManWithoutAPast".titleize # => "The Man Without A Past"
111
- # "raiders_of_the_lost_ark".titleize # => "Raiders Of The Lost Ark"
112
- def titleize(word)
113
- humanize(underscore(word)).gsub(/\b('?[a-z])/) { $1.capitalize }
114
- end
115
-
116
- # Create the name of a table like Rails does for models to table names. This method
117
- # uses the +pluralize+ method on the last word in the string.
118
- #
119
- # Examples
120
- # "RawScaledScorer".tableize # => "raw_scaled_scorers"
121
- # "egg_and_ham".tableize # => "egg_and_hams"
122
- # "fancyCategory".tableize # => "fancy_categories"
123
- def tableize(class_name)
124
- pluralize(underscore(class_name))
125
- end
126
-
127
- # Create a class name from a plural table name like Rails does for table names to models.
128
- # Note that this returns a string and not a Class. (To convert to an actual class
129
- # follow +classify+ with +constantize+.)
130
- #
131
- # Examples:
132
- # "egg_and_hams".classify # => "EggAndHam"
133
- # "posts".classify # => "Post"
134
- #
135
- # Singular names are not handled correctly:
136
- # "business".classify # => "Busines"
137
- def classify(table_name)
138
- # strip out any leading schema name
139
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
140
- end
141
-
142
- # Replaces underscores with dashes in the string.
143
- #
144
- # Example:
145
- # "puni_puni" # => "puni-puni"
146
- def dasherize(underscored_word)
147
- underscored_word.gsub(/_/, '-')
148
- end
149
-
150
- # Removes the module part from the expression in the string:
151
- #
152
- # "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
153
- # "Inflections".demodulize # => "Inflections"
154
- #
155
- # See also +deconstantize+.
156
- def demodulize(path)
157
- path = path.to_s
158
- if i = path.rindex('::')
159
- path[(i+2)..-1]
160
- else
161
- path
162
- end
163
- end
164
-
165
- # Removes the rightmost segment from the constant expression in the string:
166
- #
167
- # "Net::HTTP".deconstantize # => "Net"
168
- # "::Net::HTTP".deconstantize # => "::Net"
169
- # "String".deconstantize # => ""
170
- # "::String".deconstantize # => ""
171
- # "".deconstantize # => ""
172
- #
173
- # See also +demodulize+.
174
- def deconstantize(path)
175
- path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename
176
- end
177
-
178
- # Creates a foreign key name from a class name.
179
- # +separate_class_name_and_id_with_underscore+ sets whether
180
- # the method should put '_' between the name and 'id'.
181
- #
182
- # Examples:
183
- # "Message".foreign_key # => "message_id"
184
- # "Message".foreign_key(false) # => "messageid"
185
- # "Admin::Post".foreign_key # => "post_id"
186
- def foreign_key(class_name, separate_class_name_and_id_with_underscore = true)
187
- underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id")
188
- end
189
-
190
- # Tries to find a constant with the name specified in the argument string:
191
- #
192
- # "Module".constantize # => Module
193
- # "Test::Unit".constantize # => Test::Unit
194
- #
195
- # The name is assumed to be the one of a top-level constant, no matter whether
196
- # it starts with "::" or not. No lexical context is taken into account:
197
- #
198
- # C = 'outside'
199
- # module M
200
- # C = 'inside'
201
- # C # => 'inside'
202
- # "C".constantize # => 'outside', same as ::C
203
- # end
204
- #
205
- # NameError is raised when the name is not in CamelCase or the constant is
206
- # unknown.
207
- def constantize(camel_cased_word) #:nodoc:
208
- names = camel_cased_word.split('::')
209
- names.shift if names.empty? || names.first.empty?
210
-
211
- constant = Object
212
- names.each do |name|
213
- constant = constant.const_defined?(name, false) ? constant.const_get(name) : constant.const_missing(name)
214
- end
215
- constant
216
- end
217
-
218
- # Tries to find a constant with the name specified in the argument string:
219
- #
220
- # "Module".safe_constantize # => Module
221
- # "Test::Unit".safe_constantize # => Test::Unit
222
- #
223
- # The name is assumed to be the one of a top-level constant, no matter whether
224
- # it starts with "::" or not. No lexical context is taken into account:
225
- #
226
- # C = 'outside'
227
- # module M
228
- # C = 'inside'
229
- # C # => 'inside'
230
- # "C".safe_constantize # => 'outside', same as ::C
231
- # end
232
- #
233
- # nil is returned when the name is not in CamelCase or the constant (or part of it) is
234
- # unknown.
235
- #
236
- # "blargle".safe_constantize # => nil
237
- # "UnknownModule".safe_constantize # => nil
238
- # "UnknownModule::Foo::Bar".safe_constantize # => nil
239
- #
240
- def safe_constantize(camel_cased_word)
241
- begin
242
- constantize(camel_cased_word)
243
- rescue NameError => e
244
- raise unless e.message =~ /uninitialized constant #{const_regexp(camel_cased_word)}$/ ||
245
- e.name.to_s == camel_cased_word.to_s
246
- rescue ArgumentError => e
247
- raise unless e.message =~ /not missing constant #{const_regexp(camel_cased_word)}\!$/
248
- end
249
- end
250
-
251
- # Turns a number into an ordinal string used to denote the position in an
252
- # ordered sequence such as 1st, 2nd, 3rd, 4th.
253
- #
254
- # Examples:
255
- # ordinalize(1) # => "1st"
256
- # ordinalize(2) # => "2nd"
257
- # ordinalize(1002) # => "1002nd"
258
- # ordinalize(1003) # => "1003rd"
259
- # ordinalize(-11) # => "-11th"
260
- # ordinalize(-1021) # => "-1021st"
261
- def ordinalize(number)
262
- if (11..13).include?(number.to_i.abs % 100)
263
- "#{number}th"
264
- else
265
- case number.to_i.abs % 10
266
- when 1; "#{number}st"
267
- when 2; "#{number}nd"
268
- when 3; "#{number}rd"
269
- else "#{number}th"
270
- end
271
- end
272
- end
273
-
274
- private
275
-
276
- # Mount a regular expression that will match part by part of the constant.
277
- # For instance, Foo::Bar::Baz will generate Foo(::Bar(::Baz)?)?
278
- def const_regexp(camel_cased_word) #:nodoc:
279
- parts = camel_cased_word.split("::")
280
- last = parts.pop
281
-
282
- parts.reverse.inject(last) do |acc, part|
283
- part.empty? ? acc : "#{part}(::#{acc})?"
284
- end
285
- end
286
-
287
- # Applies inflection rules for +singularize+ and +pluralize+.
288
- #
289
- # Examples:
290
- # apply_inflections("post", inflections.plurals) # => "posts"
291
- # apply_inflections("posts", inflections.singulars) # => "post"
292
- def apply_inflections(word, rules)
293
- result = word.to_s.dup
294
-
295
- if word.empty? || inflections.uncountables.any? { |inflection| result =~ /\b#{inflection}\Z/i }
296
- result
297
- else
298
- rules.each { |(rule, replacement)| break if result.gsub!(rule, replacement) }
299
- result
300
- end
301
- end
302
- end
303
- end
304
-
305
- require 'ax_elements/vendor/inflections'
306
- require 'ax_elements/vendor/inflection_data'