padrino-admin 0.2.9 → 0.4.5
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/Rakefile +9 -8
- data/VERSION +1 -1
- data/lib/padrino-admin.rb +11 -2
- data/lib/padrino-admin/access_control.rb +353 -0
- data/lib/padrino-admin/access_control/helpers.rb +81 -0
- data/lib/padrino-admin/adapters.rb +82 -0
- data/lib/padrino-admin/adapters/ar.rb +75 -0
- data/lib/padrino-admin/adapters/dm.rb +131 -0
- data/lib/padrino-admin/adapters/mm.rb +49 -0
- data/lib/padrino-admin/ext_js/config.rb +153 -151
- data/lib/padrino-admin/ext_js/controller.rb +167 -0
- data/lib/padrino-admin/generators/backend.rb +1 -1
- data/lib/padrino-admin/locale/en.yml +7 -0
- data/lib/padrino-admin/support.rb +12 -0
- data/lib/padrino-admin/utils/crypt.rb +29 -0
- data/padrino-admin.gemspec +27 -6
- data/test/fixtures/active_record.rb +17 -0
- data/test/fixtures/data_mapper.rb +36 -0
- data/test/fixtures/mongo_mapper.rb +12 -0
- data/test/helper.rb +44 -48
- data/test/test_access_control.rb +98 -0
- data/test/test_active_record.rb +28 -0
- data/test/test_admin_application.rb +38 -0
- data/test/test_controller.rb +28 -0
- data/test/test_data_mapper.rb +32 -0
- data/test/test_mongo_mapper.rb +28 -0
- data/test/test_parsing.rb +12 -12
- metadata +33 -5
- data/test/test_padrino_admin.rb +0 -7
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Padrino
|
5
|
+
module Admin
|
6
|
+
|
7
|
+
class AdapterError < StandardError; end
|
8
|
+
|
9
|
+
module Adapters
|
10
|
+
# This method it's used for register for the specified ORM the extensions.
|
11
|
+
def self.register(adapter, klass=nil)
|
12
|
+
klass ||= Account
|
13
|
+
case adapter
|
14
|
+
when :active_record
|
15
|
+
ActiveRecord::Base.send(:include, Padrino::Admin::Adapters::Ar::Base)
|
16
|
+
klass.send(:include, Padrino::Admin::Adapters::Ar::Account)
|
17
|
+
when :data_mapper
|
18
|
+
DataMapper::Model.descendants.each { |d| d.send(:include, Padrino::Admin::Adapters::Dm::Base) }
|
19
|
+
klass.send(:include, Padrino::Admin::Adapters::Dm::Account)
|
20
|
+
when :mongo_mapper
|
21
|
+
MongoMapper::Document.append_inclusions(Padrino::Admin::Adapters::Mm::Base)
|
22
|
+
klass.send(:include, Padrino::Admin::Adapters::Mm::Account)
|
23
|
+
else
|
24
|
+
raise Padrino::Admin::AdapterError, "The adapter #{adapter.inspect} is not supported, available adapters are: " +
|
25
|
+
":active_record, :data_mapper, :mongo_mapper"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Here standard extension
|
30
|
+
module Base
|
31
|
+
# TODO
|
32
|
+
end
|
33
|
+
# Here extension for account
|
34
|
+
#
|
35
|
+
# An Account must have these column/fields:
|
36
|
+
#
|
37
|
+
# role:: String
|
38
|
+
# email:: String (used as login)
|
39
|
+
# crypted_password:: String
|
40
|
+
# salt:: String
|
41
|
+
#
|
42
|
+
# We add to Account model some methods:
|
43
|
+
#
|
44
|
+
# Account.authenticate(email, password):: check if exist an account for the given email and return it if password match
|
45
|
+
# account.password_clean:: return the decrypted password of an Account instance
|
46
|
+
#
|
47
|
+
module AccountUtils
|
48
|
+
|
49
|
+
def self.included(base) #:nodoc:
|
50
|
+
base.extend ClassMethods
|
51
|
+
base.send :include, InstanceMethods
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
# This method it's for authentication purpose
|
56
|
+
def authenticate(email, password)
|
57
|
+
account = first(:conditions => { :email => email })
|
58
|
+
account && account.password_clean == password ? account : nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module InstanceMethods
|
63
|
+
# This method it's used for retrive the original password.
|
64
|
+
def password_clean
|
65
|
+
crypted_password.decrypt(salt)
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def generate_password
|
70
|
+
return if password.blank?
|
71
|
+
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
|
72
|
+
self.crypted_password = password.encrypt(self.salt)
|
73
|
+
end
|
74
|
+
|
75
|
+
def password_required
|
76
|
+
crypted_password.blank? || !password.blank?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Admin
|
3
|
+
module Adapters
|
4
|
+
module Ar
|
5
|
+
|
6
|
+
# Here basic functions for interact with ActiveRecord
|
7
|
+
module Base
|
8
|
+
def self.included(base)
|
9
|
+
base.send :include, Padrino::Admin::Adapters::Base
|
10
|
+
base.send :include, InstanceMethods
|
11
|
+
base.extend ClassMethods
|
12
|
+
base.class_eval do
|
13
|
+
named_scope :ext_search, lambda { |params|
|
14
|
+
conditions = nil
|
15
|
+
|
16
|
+
if !params[:query].blank? && !params[:fields].blank?
|
17
|
+
filters = params[:fields].split(",").collect { |f| "#{f} LIKE ?" }.compact
|
18
|
+
conditions = [filters.join(" OR ")].concat((1..filters.size).collect { "%#{params[:query]}%" })
|
19
|
+
end
|
20
|
+
|
21
|
+
{ :conditions => conditions }
|
22
|
+
}
|
23
|
+
named_scope :ext_paginate, lambda { |params|
|
24
|
+
params.symbolize_keys!
|
25
|
+
order = params[:sort].blank? && params[:dir].blank? ? nil : "#{params[:sort]} #{params[:dir]}"
|
26
|
+
{ :order => order, :limit => params[:limit], :offset => params[:start] }
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
|
36
|
+
# Person.human_attribute_name("first_name") # => "First name"
|
37
|
+
# This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
|
38
|
+
# module now.
|
39
|
+
# Specify +options+ with additional translating options.
|
40
|
+
def human_attribute_name(attribute_key_name, options = {})
|
41
|
+
defaults = self_and_descendants.map do |klass|
|
42
|
+
:"#{klass.name.underscore}.#{attribute_key_name}"
|
43
|
+
end
|
44
|
+
defaults << options[:default] if options[:default]
|
45
|
+
defaults.flatten!
|
46
|
+
defaults << attribute_key_name.to_s.humanize
|
47
|
+
options[:count] ||= 1
|
48
|
+
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:model, :attributes]))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Here extension for account for ActiveRecord
|
54
|
+
module Account
|
55
|
+
# Extend our class when included
|
56
|
+
def self.included(base)
|
57
|
+
base.send :include, Padrino::Admin::Adapters::AccountUtils
|
58
|
+
base.send :attr_accessor, :password
|
59
|
+
# Validations
|
60
|
+
base.validates_presence_of :email
|
61
|
+
base.validates_presence_of :password, :if => :password_required
|
62
|
+
base.validates_presence_of :password_confirmation, :if => :password_required
|
63
|
+
base.validates_length_of :password, :within => 4..40, :if => :password_required
|
64
|
+
base.validates_confirmation_of :password, :if => :password_required
|
65
|
+
base.validates_length_of :email, :within => 3..100
|
66
|
+
base.validates_uniqueness_of :email, :case_sensitive => false
|
67
|
+
base.validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
68
|
+
# Callbacks
|
69
|
+
base.before_save :generate_password
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Admin
|
3
|
+
module Adapters
|
4
|
+
module Dm
|
5
|
+
|
6
|
+
# Here basic functions for interact with DataMapper
|
7
|
+
module Base
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.send :include, Padrino::Admin::Adapters::Base
|
11
|
+
base.send :include, InstanceMethods
|
12
|
+
base.extend ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
# This method allow us to don't see deprecations
|
17
|
+
def new_record?; new?; end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
def self_and_descendants #:nodoc:
|
22
|
+
klass = self
|
23
|
+
classes = [klass]
|
24
|
+
while klass != klass.base_class
|
25
|
+
classes << klass = klass.superclass
|
26
|
+
end
|
27
|
+
classes
|
28
|
+
rescue
|
29
|
+
# OPTIMIZE this rescue is to fix this test: ./test/cases/reflection_test.rb:56:in `test_human_name_for_column'
|
30
|
+
# Appearantly the method base_class causes some trouble.
|
31
|
+
# It now works for sure.
|
32
|
+
[self]
|
33
|
+
end
|
34
|
+
|
35
|
+
# Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
|
36
|
+
# Person.human_attribute_name("first_name") # => "First name"
|
37
|
+
# This used to be depricated in favor of humanize, but is now preferred, because it automatically uses the I18n
|
38
|
+
# module now.
|
39
|
+
# Specify +options+ with additional translating options.
|
40
|
+
def human_attribute_name(attribute_key_name, options = {})
|
41
|
+
defaults = self_and_descendants.map do |klass|
|
42
|
+
:"#{klass.name.underscore}.#{attribute_key_name}"
|
43
|
+
end
|
44
|
+
defaults << options[:default] if options[:default]
|
45
|
+
defaults.flatten!
|
46
|
+
defaults << attribute_key_name.to_s.humanize
|
47
|
+
options[:count] ||= 1
|
48
|
+
I18n.translate(defaults.shift, options.merge(:default => defaults, :scope => [:model, :attributes]))
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the name of the sql table
|
52
|
+
def table_name
|
53
|
+
storage_names[:default]
|
54
|
+
end
|
55
|
+
|
56
|
+
# Perform a basic fulltext search/ordering for the given columns
|
57
|
+
#
|
58
|
+
# +params+ is an hash like that:
|
59
|
+
# { :sort => "name", :dir => "ASC", :fields=>"name,surname,company", :query => 'daddye' }
|
60
|
+
#
|
61
|
+
# In this example we search in columns name, surname, company the string daddye and then we order by
|
62
|
+
# column +name+
|
63
|
+
def ext_search(params)
|
64
|
+
params.symbolize_keys!
|
65
|
+
|
66
|
+
# We need a basic query
|
67
|
+
query = {}
|
68
|
+
|
69
|
+
if params[:query].present? && params[:fields].present?
|
70
|
+
# Here we build some like: ["name LIKE ?", "surname LIKE ?"]
|
71
|
+
fields = params[:fields].split(",").collect { |f| "#{f.strip.downcase} LIKE ?" }
|
72
|
+
# Here we build some like: ["name LIKE ? OR surname LIKE ?", "%foo%", "%foo%"]
|
73
|
+
query[:conditions] = [fields.join(" OR ")].concat(1.upto(fields.length).collect { "%#{params[:query]}%" })
|
74
|
+
end
|
75
|
+
|
76
|
+
# Now we can perform a search
|
77
|
+
all(query)
|
78
|
+
end
|
79
|
+
|
80
|
+
def ext_paginate(params)
|
81
|
+
params.symbolize_keys!
|
82
|
+
|
83
|
+
# We need a basic query
|
84
|
+
query = {}
|
85
|
+
|
86
|
+
# First we need to sort our record
|
87
|
+
if params[:sort].present? && params[:dir].to_s =~ /^(asc|desc)$/
|
88
|
+
query[:order] = [params[:sort].to_sym.send(params[:dir].to_s.downcase)]
|
89
|
+
end
|
90
|
+
|
91
|
+
# Now time to limit/offset it
|
92
|
+
query[:limit] = params[:limit].to_i if params[:limit].to_i > 0
|
93
|
+
query[:offset] = params[:offset].to_i if params[:offset].to_i > 0
|
94
|
+
|
95
|
+
# Now we can perform ording/limiting
|
96
|
+
all(query)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# Here extension for account for DataMapper
|
102
|
+
module Account
|
103
|
+
# Extend our class when included
|
104
|
+
def self.included(base)
|
105
|
+
super
|
106
|
+
base.send :include, Padrino::Admin::Adapters::AccountUtils
|
107
|
+
base.send :include, DataMapper::Validate
|
108
|
+
base.send :attr_accessor, :password, :password_confirmation
|
109
|
+
# Properties
|
110
|
+
base.property :email, String
|
111
|
+
base.property :crypted_password, String
|
112
|
+
base.property :salt, String
|
113
|
+
base.property :role, String
|
114
|
+
# Validations
|
115
|
+
base.validates_present :email, :role
|
116
|
+
base.validates_present :password, :if => :password_required
|
117
|
+
base.validates_present :password_confirmation, :if => :password_required
|
118
|
+
base.validates_length :password, :min => 4, :max => 40, :if => :password_required
|
119
|
+
base.validates_is_confirmed :password, :if => :password_required
|
120
|
+
base.validates_length :email, :min => 3, :max => 100
|
121
|
+
base.validates_is_unique :email, :case_sensitive => false
|
122
|
+
base.validates_format :email, :with => :email_address
|
123
|
+
base.validates_format :role, :with => /[A-Za-z]/
|
124
|
+
# Callbacks
|
125
|
+
base.before :save, :generate_password
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Padrino
|
2
|
+
module Admin
|
3
|
+
module Adapters
|
4
|
+
module Mm
|
5
|
+
|
6
|
+
# Here basic functions for interact with MongoMapper
|
7
|
+
module Base
|
8
|
+
def self.included(base)
|
9
|
+
base.send :include, Padrino::Admin::Adapters::Base
|
10
|
+
base.send :include, InstanceMethods
|
11
|
+
base.extend ClassMethods
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Here extension for account for DataMapper
|
22
|
+
module Account
|
23
|
+
# Extend our class when included
|
24
|
+
def self.included(base)
|
25
|
+
super
|
26
|
+
base.send :include, Padrino::Admin::Adapters::AccountUtils
|
27
|
+
base.send :attr_accessor, :password, :password_confirmation
|
28
|
+
# Properties
|
29
|
+
base.key :email, String
|
30
|
+
base.key :crypted_password, String
|
31
|
+
base.key :salt, String
|
32
|
+
base.key :role, String
|
33
|
+
# Validations
|
34
|
+
base.validates_presence_of :email
|
35
|
+
base.validates_presence_of :password, :if => :password_required
|
36
|
+
base.validates_presence_of :password_confirmation, :if => :password_required
|
37
|
+
base.validates_length_of :password, :within => 4..40, :if => :password_required
|
38
|
+
base.validates_confirmation_of :password, :if => :password_required
|
39
|
+
base.validates_length_of :email, :within => 3..100
|
40
|
+
base.validates_uniqueness_of :email, :case_sensitive => false
|
41
|
+
base.validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
42
|
+
# Callbacks
|
43
|
+
base.before_save :generate_password
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -3,167 +3,169 @@ require 'yaml'
|
|
3
3
|
require 'erb'
|
4
4
|
require 'json/pure'
|
5
5
|
|
6
|
-
module
|
6
|
+
module Padrino
|
7
|
+
module ExtJs
|
7
8
|
|
8
|
-
|
9
|
+
class ConfigError < RuntimeError; end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
11
|
+
# This class it's used for JSON variables.
|
12
|
+
# Normally if we convert this { :function => "alert('Test')" } will be:
|
13
|
+
#
|
14
|
+
# { "function": "alert('Test')" }
|
15
|
+
#
|
16
|
+
# But if in our javascript need to "eval" this function is not possible because
|
17
|
+
# it's a string.
|
18
|
+
#
|
19
|
+
# Using Padrino::ExtJs::Variable the result will be:
|
20
|
+
#
|
21
|
+
# { "function" : alert('Test') }
|
22
|
+
#
|
23
|
+
# Normally an ExtJs Variable can be handled with ExtJs Config like:
|
24
|
+
#
|
25
|
+
# function: !js alert('Test')
|
26
|
+
#
|
27
|
+
class Variable < String
|
28
|
+
yaml_as "tag:yaml.org,2002:js"
|
28
29
|
|
29
|
-
|
30
|
-
|
30
|
+
def to_json(*a) #:nodoc:
|
31
|
+
self
|
32
|
+
end
|
31
33
|
end
|
32
|
-
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
# Initialize a new config parsing an Hash
|
121
|
-
def initialize(data)
|
122
|
-
@data = data
|
123
|
-
parsed = parse(@data)
|
124
|
-
super
|
125
|
-
replace parsed
|
126
|
-
end
|
35
|
+
# This class it's used for write in a new and simple way json.
|
36
|
+
#
|
37
|
+
# In ExtJs framework generally each component have a configuration written in json.
|
38
|
+
#
|
39
|
+
# Write this config in ruby it's not the best choice think this example:
|
40
|
+
#
|
41
|
+
# # A Generic grid config in JavaScript:
|
42
|
+
# var gridPanel = new Ext.grid.GridPanel({
|
43
|
+
# bbar: gridPanelPagingToolbar,
|
44
|
+
# clicksToEdit: 1,
|
45
|
+
# cm: gridPanelColumnModel,
|
46
|
+
# region: "center",
|
47
|
+
# sm: gridPanelCheckboxSelectionModel,
|
48
|
+
# viewConfig: {"forceFit":true},
|
49
|
+
# plugins: [new Ext.grid.Search()],
|
50
|
+
# border: false,
|
51
|
+
# tbar: gridPanelToolbar,
|
52
|
+
# id: "grid-accounts",
|
53
|
+
# bodyBorder: false,
|
54
|
+
# store: gridPanelGroupingStore,
|
55
|
+
# view: gridPanelGroupingView
|
56
|
+
# });
|
57
|
+
#
|
58
|
+
# # A Gneric grid config in Ruby:
|
59
|
+
# { :bbar => Padrino::ExtJs::Variable.new('gridPanelPagingToolbar'), :clicksToEdit => 1,
|
60
|
+
# :cm => Padrino::ExtJs::Variable.new('gridPanelColumnModel'), :region => "center",
|
61
|
+
# :sm => Padrino::ExtJs::Variable.new('gridPanelCheckboxSelectionModel'),
|
62
|
+
# :viewConfig => { :forceFit => true }, plugins => [Padrino::ExtJs::Variable.new('new Ext.grid.Search()'].
|
63
|
+
# :border => false ... more more code...
|
64
|
+
#
|
65
|
+
# As you can see writing json in pure ruby (in this case with hash) require much time and is
|
66
|
+
# <tt>less</tt> readable.
|
67
|
+
#
|
68
|
+
# For this reason we build an ExtJs Config, that basically it's an yaml file with
|
69
|
+
# some new great functions so the example above will be:
|
70
|
+
#
|
71
|
+
# # A Generic grid config in ExtJs Config:
|
72
|
+
# gridPanel:
|
73
|
+
# bbar: !js gridPanelPagingToolbar
|
74
|
+
# clicksToEdit: 1,
|
75
|
+
# cm: !js gridPanelColumnModel
|
76
|
+
# region: center
|
77
|
+
# sm: !js gridPanelCheckboxSelectionModel
|
78
|
+
# viewConfig:
|
79
|
+
# forceFit: true
|
80
|
+
# plugins: [!js new Ext.grid.Search()]
|
81
|
+
# border: false
|
82
|
+
# tbar: !js gridPanelToolbar
|
83
|
+
# id: grid-accounts
|
84
|
+
# bodyBorder: false
|
85
|
+
# store: !js gridPanelGroupingStore
|
86
|
+
# view: !js gridPanelGroupingView
|
87
|
+
#
|
88
|
+
# Now you see that it's more readable, simple and coincise!
|
89
|
+
#
|
90
|
+
# But our ExtJs config can act also as an xml or an erb partial. See this code:
|
91
|
+
#
|
92
|
+
# # A template
|
93
|
+
# tempate:
|
94
|
+
# tbar:
|
95
|
+
# here: a custom config
|
96
|
+
# grid:
|
97
|
+
# viewConfig:
|
98
|
+
# forceFit: true
|
99
|
+
# plugins: [!js new Ext.grid.Search()]
|
100
|
+
# border: false
|
101
|
+
#
|
102
|
+
# We can "grep" this config in our template with:
|
103
|
+
#
|
104
|
+
# # A generic grid
|
105
|
+
# gridPanel:
|
106
|
+
# <<: %template/grid
|
107
|
+
# border: true
|
108
|
+
#
|
109
|
+
# The result will be:
|
110
|
+
#
|
111
|
+
# gridPanel:
|
112
|
+
# viewConfig:
|
113
|
+
# forceFit: true
|
114
|
+
# plugins: [!js new Ext.grid.Search()]
|
115
|
+
# border: true # overidden
|
116
|
+
#
|
117
|
+
# See our test for more complex examples.
|
118
|
+
#
|
119
|
+
class Config < Hash
|
127
120
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
121
|
+
# Initialize a new config parsing an Hash
|
122
|
+
def initialize(data)
|
123
|
+
@data = data
|
124
|
+
parsed = parse(@data)
|
125
|
+
super
|
126
|
+
replace parsed
|
127
|
+
end
|
132
128
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
129
|
+
# Load a new config from an yml file and return a parsed hash.
|
130
|
+
def self.load_file(path, binding=nil)
|
131
|
+
self.load(File.read(path), binding)
|
132
|
+
end
|
137
133
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
134
|
+
# Load a new config from a yaml "string" and return a parsed hash.
|
135
|
+
def self.load(string, binding=nil)
|
136
|
+
self.new YAML.parse(ERB.new(string).result(binding))
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def parse(node=nil, key=nil)
|
141
|
+
case node.value
|
142
|
+
when String
|
143
|
+
if node.value =~ /^%{1}(.*)/
|
144
|
+
node = parse(@data.select($1).first)
|
145
|
+
end
|
146
|
+
node.respond_to?(:transform) ? node.transform : node
|
147
|
+
when Hash
|
148
|
+
parsed = {}
|
149
|
+
node.value.each do |k,v|
|
150
|
+
if k.value == "<<"
|
151
|
+
node = parse(v)
|
152
|
+
if node.is_a?(Hash)
|
153
|
+
node.merge!(parsed)
|
154
|
+
end
|
155
|
+
parsed = node
|
156
|
+
else
|
157
|
+
parsed[k.value] = parse(v)
|
153
158
|
end
|
154
|
-
parsed = node
|
155
|
-
else
|
156
|
-
parsed[k.value] = parse(v)
|
157
159
|
end
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
160
|
+
parsed
|
161
|
+
when Array
|
162
|
+
parsed = []
|
163
|
+
node.value.each do |v|
|
164
|
+
node = parse(v)
|
165
|
+
node.is_a?(Array) ? parsed.concat(node) : parsed.push(node)
|
166
|
+
end
|
167
|
+
parsed
|
168
|
+
end
|
167
169
|
end
|
168
170
|
end
|
169
171
|
end
|