langrove 0.0.2
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.
- 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