langrove 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +1 -0
- data/.rvmrc +62 -0
- data/.watchr +27 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +61 -0
- data/Rakefile +24 -0
- data/functional/.gitignore +1 -0
- data/functional/bin/datagram +6 -0
- data/functional/config/.gitignore +0 -0
- data/functional/config/boot.rb +64 -0
- data/functional/config/daemons.yml +12 -0
- data/functional/config/environment.rb +28 -0
- data/functional/config/environments/development.rb +0 -0
- data/functional/config/environments/production.rb +0 -0
- data/functional/config/environments/test.rb +0 -0
- data/functional/lib/daemon/datagram.rb +21 -0
- data/functional/lib/handler/socket_to_file.rb +36 -0
- data/functional/lib/protocol/socket_to_file.rb +55 -0
- data/functional/libexec/daemon.rb +68 -0
- data/functional/log/.gitignore +3 -0
- data/functional/tmp/README +1 -0
- data/lib/langrove/_base.rb +26 -0
- data/lib/langrove/adaptor/base.rb +3 -0
- data/lib/langrove/adaptor/datagram.rb +27 -0
- data/lib/langrove/adaptor_base.rb +89 -0
- data/lib/langrove/client/base.rb +2 -0
- data/lib/langrove/client/datagram.rb +25 -0
- data/lib/langrove/client_base.rb +114 -0
- data/lib/langrove/daemon/base.rb +2 -0
- data/lib/langrove/daemon_base.rb +175 -0
- data/lib/langrove/ext/class_loader.rb +148 -0
- data/lib/langrove/ext/config_item.rb +34 -0
- data/lib/langrove/ext/config_loader.rb +16 -0
- data/lib/langrove/ext/fake_logger.rb +8 -0
- data/lib/langrove/ext/persistable.rb +103 -0
- data/lib/langrove/ext/string.rb +35 -0
- data/lib/langrove/ext.rb +7 -0
- data/lib/langrove/handler/base.rb +2 -0
- data/lib/langrove/handler_base.rb +141 -0
- data/lib/langrove/protocol/base.rb +2 -0
- data/lib/langrove/protocol/syslog.rb +32 -0
- data/lib/langrove/protocol_base.rb +32 -0
- data/lib/langrove/version.rb +3 -0
- data/lib/langrove.rb +1 -0
- data/spec/functional/daemon/datagram_spec.rb +115 -0
- data/spec/langrove/adaptor/datagram_spec.rb +6 -0
- data/spec/langrove/adaptor_base_spec.rb +48 -0
- data/spec/langrove/client/datagram_spec.rb +1 -0
- data/spec/langrove/client_base_spec.rb +5 -0
- data/spec/langrove/daemon_base_spec.rb +101 -0
- data/spec/langrove/ext/class_loader_spec.rb +83 -0
- data/spec/langrove/ext/config_item_spec.rb +81 -0
- data/spec/langrove/ext/config_loader_spec.rb +5 -0
- data/spec/langrove/ext/fake_logger_spec.rb +0 -0
- data/spec/langrove/ext/persistable_spec.rb +117 -0
- data/spec/langrove/ext/string_spec.rb +16 -0
- data/spec/langrove/handler_base_spec.rb +57 -0
- data/spec/langrove/protocol/syslog_spec.rb +45 -0
- data/spec/langrove/protocol_base_spec.rb +6 -0
- data/spec/todo_spec.rb +12 -0
- data/tmp/README +2 -0
- metadata +200 -0
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'langrove'
|
3
|
+
|
4
|
+
module LanGrove
|
5
|
+
|
6
|
+
module Client
|
7
|
+
|
8
|
+
class Base < EventMachine::Connection
|
9
|
+
|
10
|
+
#
|
11
|
+
# Misnomer.
|
12
|
+
#
|
13
|
+
#
|
14
|
+
# This is not the client,
|
15
|
+
#
|
16
|
+
#
|
17
|
+
# It is the server's perspective of the client,
|
18
|
+
#
|
19
|
+
# These are generally stored in the daemon's
|
20
|
+
# handler collection,
|
21
|
+
#
|
22
|
+
# Which contains this and all similar clients,
|
23
|
+
#
|
24
|
+
# Each bonded through the daemon's connection
|
25
|
+
# adaptor to the socket that couples them to
|
26
|
+
# their actual client..
|
27
|
+
#
|
28
|
+
|
29
|
+
#
|
30
|
+
# The handler collection specified in config
|
31
|
+
# is bound to this attribute after connect.
|
32
|
+
#
|
33
|
+
attr_accessor :handler
|
34
|
+
attr_accessor :protocol
|
35
|
+
attr_accessor :config
|
36
|
+
|
37
|
+
#
|
38
|
+
# Data arriving through the Adaptor is passed
|
39
|
+
# through the config assigned protocol and
|
40
|
+
# then arrives here in whatever format the
|
41
|
+
# <Protocol>.decode() method returned.
|
42
|
+
#
|
43
|
+
#
|
44
|
+
def receive( decoded_data )
|
45
|
+
|
46
|
+
#
|
47
|
+
# OVERRIDE
|
48
|
+
#
|
49
|
+
# - Inbound data arrived at the adaptor
|
50
|
+
#
|
51
|
+
# - It has passed through the protocol as
|
52
|
+
# defined in config.
|
53
|
+
#
|
54
|
+
# The protocol arranged the data into
|
55
|
+
# a Hash of:
|
56
|
+
#
|
57
|
+
# - key and value pairs
|
58
|
+
#
|
59
|
+
# - key and more_complicated_thing pairs.
|
60
|
+
#
|
61
|
+
# - The decoded data was then passed into
|
62
|
+
# this function.
|
63
|
+
#
|
64
|
+
#
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def receive_data( data )
|
69
|
+
|
70
|
+
#
|
71
|
+
# Note: The protocol currently lives on the Handler
|
72
|
+
#
|
73
|
+
# It MIG( ...in progress... )HT move,
|
74
|
+
#
|
75
|
+
# To a separate instance of the protocol object
|
76
|
+
# residing in each Client::ClientType instance
|
77
|
+
# in the Handler::Base.client collection.
|
78
|
+
#
|
79
|
+
# Pending examining the logical barrier of having
|
80
|
+
# the Handler route to the Cleint per a key value
|
81
|
+
# extracted from the decoded data - as decoded by
|
82
|
+
# the client's instance of the protocol not yet
|
83
|
+
# accessable within the as yet undeterminable
|
84
|
+
# instance in the Handlers collection.
|
85
|
+
#
|
86
|
+
# Catch22(ish)
|
87
|
+
#
|
88
|
+
# But that was suitable as a pettern for the
|
89
|
+
# datagram server.
|
90
|
+
#
|
91
|
+
# Perhaps after implementing Adapter::TcpServer
|
92
|
+
#
|
93
|
+
# And gaining a more knowsome notion of the
|
94
|
+
# intricacies of the application layer, esp
|
95
|
+
# ACKing and NAKing.
|
96
|
+
#
|
97
|
+
# Then the possibilities of the Client::ClientType
|
98
|
+
# being a decendant of <self> --
|
99
|
+
#
|
100
|
+
# ie. The EM::Connection as instanciated upon each
|
101
|
+
# socket connect.
|
102
|
+
#
|
103
|
+
# -- will likely resolve.
|
104
|
+
#
|
105
|
+
#
|
106
|
+
receive( @protocol.decode( data ) )
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
|
3
|
+
require 'langrove'
|
4
|
+
|
5
|
+
module LanGrove::Daemon class Base
|
6
|
+
|
7
|
+
def listen
|
8
|
+
|
9
|
+
#
|
10
|
+
# Handler - The CollectionOfClients, is passed
|
11
|
+
# through the adaptor to be assigned
|
12
|
+
# to the attaching Clients.
|
13
|
+
#
|
14
|
+
@adaptor.listen( @handler, @handler.protocol )
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop_daemon
|
19
|
+
|
20
|
+
#
|
21
|
+
# Handle stopping daemon
|
22
|
+
#
|
23
|
+
@logger.info "#{self} received stop_daemon"
|
24
|
+
@handler.stop_daemon
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def reload_daemon
|
29
|
+
|
30
|
+
#
|
31
|
+
# TODO: Handle reloading daemon properly
|
32
|
+
#
|
33
|
+
# [ !!complexities abound around reloading config!! ]
|
34
|
+
#
|
35
|
+
|
36
|
+
@logger.info "#{self} received reload_daemon"
|
37
|
+
@handler.reload_daemon
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def schedule
|
42
|
+
|
43
|
+
EM::add_periodic_timer( @my_config['periodic'] ) do
|
44
|
+
|
45
|
+
@logger.info "#{self} Scheduling periodic ...TODO..."
|
46
|
+
|
47
|
+
#
|
48
|
+
# TODO: This can be a lot more usefull than
|
49
|
+
# then one ticker to one function call
|
50
|
+
#
|
51
|
+
|
52
|
+
@handler.periodic
|
53
|
+
|
54
|
+
end if @my_config.has_key? 'periodic'
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def run
|
59
|
+
|
60
|
+
EventMachine::run do
|
61
|
+
|
62
|
+
#
|
63
|
+
# https://github.com/eventmachine/eventmachine/wiki/Protocol-Implementations
|
64
|
+
#
|
65
|
+
|
66
|
+
schedule
|
67
|
+
|
68
|
+
listen if @server
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize( config_hash, daemon_name, logger )
|
75
|
+
|
76
|
+
@config = config_hash
|
77
|
+
@daemon_name = daemon_name
|
78
|
+
@logger = logger
|
79
|
+
@server = true
|
80
|
+
|
81
|
+
#
|
82
|
+
# A network connection.
|
83
|
+
#
|
84
|
+
@adaptor = nil
|
85
|
+
|
86
|
+
#
|
87
|
+
# A handler to direct the traffic.
|
88
|
+
#
|
89
|
+
@handler = nil
|
90
|
+
|
91
|
+
|
92
|
+
begin
|
93
|
+
|
94
|
+
@logger.info "Starting daemon: #{@daemon_name}"
|
95
|
+
|
96
|
+
rescue
|
97
|
+
|
98
|
+
#
|
99
|
+
# you shall not pass
|
100
|
+
#
|
101
|
+
raise LanGrove::DaemonConfigException.new( "Requires a logger" )
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
begin
|
106
|
+
|
107
|
+
@my_config = @config[ :daemons ][ @daemon_name ]
|
108
|
+
|
109
|
+
@server = @my_config[ :server ] if @my_config.has_key? :server
|
110
|
+
|
111
|
+
adaptor = @my_config[ :adaptor ][ :connection ]
|
112
|
+
|
113
|
+
handler = @my_config[ :handler ][ :collection ]
|
114
|
+
|
115
|
+
rescue
|
116
|
+
|
117
|
+
error = "Missing config item for daemon: #{@daemon_name}"
|
118
|
+
|
119
|
+
@logger.error "EXIT: #{error}"
|
120
|
+
|
121
|
+
raise LanGrove::DaemonConfigException.new(
|
122
|
+
|
123
|
+
"Missing config item for daemon: #{@daemon_name}"
|
124
|
+
|
125
|
+
)
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
#
|
130
|
+
# initialize the connection adaptor specified in config
|
131
|
+
#
|
132
|
+
# daemons:
|
133
|
+
# name_of_daemon:
|
134
|
+
# adaptor:
|
135
|
+
# connection: TcpServer <----------
|
136
|
+
# handler:
|
137
|
+
# collection: CollectionOfClients
|
138
|
+
#
|
139
|
+
|
140
|
+
@logger.info "Initialize instance: Adaptor::#{adaptor}.new( @my_config[ :adaptor ], logger )"
|
141
|
+
|
142
|
+
@adaptor = LanGrove::ClassLoader.create( {
|
143
|
+
|
144
|
+
:module => 'Adaptor',
|
145
|
+
:class => adaptor
|
146
|
+
|
147
|
+
} ).new( @my_config[ :adaptor ], logger )
|
148
|
+
|
149
|
+
|
150
|
+
#
|
151
|
+
# bind to the handler collection specified in config
|
152
|
+
#
|
153
|
+
# daemons:
|
154
|
+
# name_of_daemon:
|
155
|
+
# adaptor:
|
156
|
+
# connection: TcpServer
|
157
|
+
# handler:
|
158
|
+
# collection: CollectionOfClients <----------
|
159
|
+
#
|
160
|
+
# CollectionOfClients ---> is the handler passed into listen()
|
161
|
+
#
|
162
|
+
|
163
|
+
@logger.info "Initialize instance: Handler::#{handler}.new( config_hash, '#{daemon_name}', logger )"
|
164
|
+
|
165
|
+
@handler = LanGrove::ClassLoader.create( {
|
166
|
+
|
167
|
+
:module => 'Handler',
|
168
|
+
:class => handler
|
169
|
+
|
170
|
+
} ).new( @config, @daemon_name, @logger )
|
171
|
+
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
end; end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'langrove/ext/string'
|
2
|
+
module LanGrove
|
3
|
+
|
4
|
+
class ClassLoaderException < Exception; end
|
5
|
+
|
6
|
+
class ClassLoader
|
7
|
+
|
8
|
+
def self.create( class_config, logger = nil )
|
9
|
+
|
10
|
+
logger.info( "#{self} is loading Class with #{class_config.inspect}" ) unless logger.nil?
|
11
|
+
|
12
|
+
#
|
13
|
+
# When <config> = {
|
14
|
+
#
|
15
|
+
# :module => 'ModuleName'
|
16
|
+
# :class => 'ClassName'
|
17
|
+
#
|
18
|
+
# }
|
19
|
+
#
|
20
|
+
# Then this will return the constantized
|
21
|
+
# definition instance of ClassName as
|
22
|
+
# loaded from the F1RST found .rb file
|
23
|
+
# according to:
|
24
|
+
#
|
25
|
+
# - lib/module_name/class_name.rb
|
26
|
+
# - langrove/module_name/class_name.rb
|
27
|
+
#
|
28
|
+
# Which then facilitates the following
|
29
|
+
# construct:
|
30
|
+
#
|
31
|
+
# planet = ClassLoader.create(
|
32
|
+
#
|
33
|
+
# :module => 'Planet',
|
34
|
+
# :class => 'Mercury'
|
35
|
+
#
|
36
|
+
# ).new( *initializer_parameters )
|
37
|
+
#
|
38
|
+
|
39
|
+
raise ClassLoaderException.new(
|
40
|
+
|
41
|
+
"class_config requires :module"
|
42
|
+
|
43
|
+
) unless class_config.has_key? :module
|
44
|
+
|
45
|
+
raise ClassLoaderException.new(
|
46
|
+
|
47
|
+
"class_config requires :class"
|
48
|
+
|
49
|
+
) unless class_config.has_key? :class
|
50
|
+
|
51
|
+
|
52
|
+
module_name = class_config[:module]
|
53
|
+
class_name = class_config[:class]
|
54
|
+
|
55
|
+
|
56
|
+
#
|
57
|
+
# SIGNIFICANT DECISION
|
58
|
+
#
|
59
|
+
# - Late binding to the extent of also calling
|
60
|
+
# to require the actual class.rb file could
|
61
|
+
# potentially be avoided by
|
62
|
+
#
|
63
|
+
# << using this layer in the abstraction >>
|
64
|
+
#
|
65
|
+
# to do the necessary requiring for the specific
|
66
|
+
# daemon being spawned.
|
67
|
+
#
|
68
|
+
# - Obviously there are downsides to eval...
|
69
|
+
#
|
70
|
+
# - But there are upsides to having this layer
|
71
|
+
# totally aliteral - it leaves the window open
|
72
|
+
# to the later posibility of collecting the class
|
73
|
+
# definition itself from across the network.
|
74
|
+
#
|
75
|
+
#
|
76
|
+
# Which was the cental purpose behind daemons
|
77
|
+
# by configuration in the first place.
|
78
|
+
#
|
79
|
+
# Taking latebinding to a whole new level...
|
80
|
+
#
|
81
|
+
|
82
|
+
|
83
|
+
#
|
84
|
+
# First try local implementation root
|
85
|
+
#
|
86
|
+
|
87
|
+
exception = nil
|
88
|
+
|
89
|
+
location = nil
|
90
|
+
|
91
|
+
begin
|
92
|
+
|
93
|
+
location = "#{module_name.underscore}/#{class_name.underscore}"
|
94
|
+
|
95
|
+
eval "require '#{location}'"
|
96
|
+
|
97
|
+
return Object.const_get( module_name ).const_get( class_name )
|
98
|
+
|
99
|
+
rescue LoadError => e
|
100
|
+
|
101
|
+
exception = e
|
102
|
+
|
103
|
+
logger.error "Missing class definition in lib/#{location}.rb" unless logger.nil?
|
104
|
+
|
105
|
+
#rescue Exception => e
|
106
|
+
#
|
107
|
+
# #
|
108
|
+
# # Incase of more... (discovery phase)
|
109
|
+
# #
|
110
|
+
#
|
111
|
+
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Fall back to langrove gem lib
|
117
|
+
#
|
118
|
+
|
119
|
+
begin
|
120
|
+
|
121
|
+
path = File.expand_path('../../', __FILE__)
|
122
|
+
|
123
|
+
location = "#{path}/#{module_name.underscore}/#{class_name.underscore}"
|
124
|
+
|
125
|
+
eval "require '#{location}'"
|
126
|
+
|
127
|
+
return LanGrove.const_get( module_name ).const_get( class_name )
|
128
|
+
|
129
|
+
rescue Exception => e
|
130
|
+
|
131
|
+
#
|
132
|
+
# Raise from the original exception to
|
133
|
+
# inform the local implementation
|
134
|
+
# it is missing a module/class .rb
|
135
|
+
# "no such file 'module/class"
|
136
|
+
#
|
137
|
+
# And not confuse the issue by raising
|
138
|
+
# "no such file 'langrove/module/class"
|
139
|
+
#
|
140
|
+
raise ClassLoaderException.new "#{exception.message}.rb"
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module LanGrove class ConfigException < Exception; end; end
|
3
|
+
|
4
|
+
module LanGrove class ConfigItem
|
5
|
+
|
6
|
+
def self.get(config_hash, key_array, mandatory = true)
|
7
|
+
|
8
|
+
# raise ConfigException.new("match")
|
9
|
+
|
10
|
+
hash = config_hash
|
11
|
+
|
12
|
+
key_array.each do |key|
|
13
|
+
|
14
|
+
if mandatory != false then
|
15
|
+
# raises a config exception if the item isnt present
|
16
|
+
raise ConfigException.new("Missing config item '#{key}'") unless hash.has_key?(key)
|
17
|
+
|
18
|
+
# raises a config exception if the nested item isnt present NEITHER NOT
|
19
|
+
# raise ConfigException.new("Missing config item '#{key}'") if hash[key].empty?
|
20
|
+
|
21
|
+
hash = hash[key]
|
22
|
+
|
23
|
+
# raises a config exception if the key isnt present BLANK NEITHER
|
24
|
+
raise ConfigException.new("Missing config item '#{key}'") if hash == ""
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
return hash
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end; end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'resque'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
# require 'updated_puppet_state'
|
5
|
+
|
6
|
+
module LanGrove class Persistable
|
7
|
+
|
8
|
+
def initialize( notifies = [] )
|
9
|
+
|
10
|
+
@notifies = notifies
|
11
|
+
|
12
|
+
@notifies.each do |worker|
|
13
|
+
|
14
|
+
Object.const_set(
|
15
|
+
|
16
|
+
worker.camelize, Class.new
|
17
|
+
|
18
|
+
) ### ??unless defined already??
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_hash( hash_instance, from_file )
|
25
|
+
#
|
26
|
+
# <hash_instance> as String (name of) variable storing the Hash
|
27
|
+
#
|
28
|
+
|
29
|
+
@logger.debug "loading from: #{from_file}" if @logger
|
30
|
+
|
31
|
+
#
|
32
|
+
# get the instance variable (late bind)
|
33
|
+
#
|
34
|
+
hash = self.instance_variable_get( hash_instance.to_sym )
|
35
|
+
hash ||= {}
|
36
|
+
|
37
|
+
begin
|
38
|
+
|
39
|
+
#
|
40
|
+
# load file contents, merge into hash and
|
41
|
+
# store it at the instance variable
|
42
|
+
#
|
43
|
+
persisted = YAML.load_file( from_file )
|
44
|
+
hash.merge!( persisted ) if persisted.is_a?( Hash )
|
45
|
+
self.instance_variable_set( hash_instance.to_sym, hash )
|
46
|
+
|
47
|
+
#
|
48
|
+
# set flag to 'already in storage'
|
49
|
+
#
|
50
|
+
@stored = true
|
51
|
+
|
52
|
+
rescue Exception => e
|
53
|
+
@logger.error "FAILED loading from #{from_file} #{e}" if @logger
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def store_hash( hash_instance, to_file )
|
60
|
+
#
|
61
|
+
# <hash_instance> as String (name of) variable storing the Hash
|
62
|
+
#
|
63
|
+
|
64
|
+
if @stored != nil && @stored
|
65
|
+
|
66
|
+
@logger.debug "storing to: #{to_file} skipped - no change"
|
67
|
+
return
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
@logger.debug "storing to: #{to_file}"
|
72
|
+
|
73
|
+
#
|
74
|
+
# get the instance variable (late bind)
|
75
|
+
#
|
76
|
+
hash = self.instance_variable_get( hash_instance.to_sym )
|
77
|
+
|
78
|
+
begin
|
79
|
+
|
80
|
+
File.open(to_file, 'w') do |f|
|
81
|
+
|
82
|
+
YAML::dump( hash, f )
|
83
|
+
@stored = true
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
#@notifies.each do |worker|
|
88
|
+
#
|
89
|
+
# Resque.enqueue(
|
90
|
+
#
|
91
|
+
# Object.const_get( worker.camelize ), to_file
|
92
|
+
#
|
93
|
+
# )
|
94
|
+
#
|
95
|
+
#end
|
96
|
+
|
97
|
+
rescue Exception => e
|
98
|
+
@logger.error "FAILED storing to: #{to_file} #{e}" if @logger
|
99
|
+
@stored = false
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end; end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# extend ruby String class
|
2
|
+
|
3
|
+
class String
|
4
|
+
#
|
5
|
+
# from the rails inflector
|
6
|
+
# (de-camelize)
|
7
|
+
#
|
8
|
+
# ie. CamelThing becomes camel_thing
|
9
|
+
#
|
10
|
+
def underscore
|
11
|
+
gsub(/::/, '/').
|
12
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
13
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
14
|
+
tr("-", "_").
|
15
|
+
downcase
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# unless already defined
|
20
|
+
#
|
21
|
+
end unless String.method_defined?(:underscore)
|
22
|
+
|
23
|
+
class String
|
24
|
+
#
|
25
|
+
# from the rails inflector
|
26
|
+
# (camelize)
|
27
|
+
#
|
28
|
+
# ie. CamelThing becomes camel_thing
|
29
|
+
#
|
30
|
+
def camelize(first_letter_in_uppercase = :upper)
|
31
|
+
s = gsub(/\/(.?)/){|x| "::#{x[-1..-1].upcase unless x == '/'}"}.gsub(/(^|_)(.)/){|x| x[-1..-1].upcase}
|
32
|
+
s[0...1] = s[0...1].downcase unless first_letter_in_uppercase == :upper
|
33
|
+
s
|
34
|
+
end
|
35
|
+
end unless String.method_defined?(:camelize)
|
data/lib/langrove/ext.rb
ADDED