Class: Construct

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/construct.rb

Constant Summary collapse

APP_NAME =

Construct is extensible, persistent, structured configuration for Ruby and humans with text editors.

'Construct'
APP_VERSION =
'0.1.7'
APP_AUTHOR =
'Kyle Kingsbury'
APP_CONTRIBUTORS =
['Kyle Kingsbury', 'Spencer Miles', 'John MacKenzie']
APP_EMAIL =
'[email protected]'
APP_URL =
'http://github.com/aphyr/construct'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data = {}, schema = {}) ⇒ Construct

Returns a new instance of Construct.



41
42
43
44
45
46
47
# File 'lib/construct.rb', line 41

def initialize(data = {}, schema = {})
  @data = Hash.new
  data.each do |key, value|
    self[key] = value
  end
  @schema = self.class.schema.merge(schema)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth, *args) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/construct.rb', line 138

def method_missing(meth, *args)
  meth_s = meth.to_s
  if meth_s[-1..-1] == '='
    # Assignment
    if args.size != 1
      raise ArgumentError.new("#{meth} takes exactly one argument")
    end

    self[meth_s[0..-2]] = args[0]
  elsif include? meth
    self[meth]
  else
    raise NoMethodError.new("no such key #{meth} in construct")
  end
end

Instance Attribute Details

#dataObject

Returns the value of attribute data.



39
40
41
# File 'lib/construct.rb', line 39

def data
  @data
end

#schemaObject

Returns the value of attribute schema.



39
40
41
# File 'lib/construct.rb', line 39

def schema
  @schema
end

Class Method Details

.define(key, schema) ⇒ Object

Define a schema for a key on the class. The class schema is used as the defaults on initialization of a new instance.



18
19
20
21
# File 'lib/construct.rb', line 18

def self.define(key, schema)
  key = key.to_sym if String === key
  self.schema[key] = schema
end

.load(yaml) ⇒ Object

Load a construct from a YAML string



24
25
26
27
# File 'lib/construct.rb', line 24

def self.load(yaml)
  hash = YAML::load(yaml)
  new(hash)
end

.load_file(filename) ⇒ Object



29
30
31
32
# File 'lib/construct.rb', line 29

def self.load_file(filename)
  hash = YAML::load_file(filename)
  new(hash)
end

.schemaObject

Returns the class schema



35
36
37
# File 'lib/construct.rb', line 35

def self.schema
  @schema ||= {}
end

Instance Method Details

#==(other) ⇒ Object



49
50
51
52
# File 'lib/construct.rb', line 49

def ==(other)
  other.respond_to? :schema and other.respond_to? :data and
    @schema == other.schema and @data == other.data
end

#[](key) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/construct.rb', line 54

def [](key)
  key = key.to_sym if String === key

  if @data.include? key
    @data[key]
  elsif @schema.include? key and @schema[key].include? :default
    @data[key] = Marshal.load(Marshal.dump(@schema[key][:default]))
  end
end

#[]=(key, value) ⇒ Object

Assign a value to a key. Constructs accept only symbols as values, and will convert strings to symbols when necessary. They will also implicitly convert Hashes as values into Constructs when possible. Hence you can do:

construct.people = => ‘Awesome’, :joe => ‘suspicious’ construct.people.mary # => ‘Awesome’

Raises:

  • (ArgumentError)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/construct.rb', line 71

def []=(key, value)
  key = key.to_sym if String === key
  raise ArgumentError.new('construct only accepts symbols (and strings) as keys.') unless key.is_a? Symbol

  # Convert suitable hashes into Constructs
  if value.is_a? Hash
    if value.keys.all? { |k|
          k.is_a? String or k.is_a? Symbol
        }
      value = Construct.new(value)
    end
  end

  @data[key] = value
end

#clearObject

Clears the data in the construct.



88
89
90
# File 'lib/construct.rb', line 88

def clear
  @data.clear
end

#define(key, options = {}) ⇒ Object

Defines a new field in the schema. Fields are :default and :desc.



93
94
95
96
# File 'lib/construct.rb', line 93

def define(key, options = {})
  key = key.to_sym if String === key
  @schema[key] = options
end

#delete(key) ⇒ Object

delete simply removes the value from the data hash, but leaves the schema unchanged. Hence the construct may still respond to include? if the schema defines that field. Use #schema.delete(:key) to remove the key entirely.



102
103
104
105
# File 'lib/construct.rb', line 102

def delete(key)
  key = key.to_sym if String === key
  @data.delete key
end

#dupObject

A deep (not shallow!) clone of this construct.



108
109
110
# File 'lib/construct.rb', line 108

def dup
  Marshal.load(Marshal.dump(self))
end

#eachObject

Like enumerable#each. Operates on each key.



113
114
115
116
117
# File 'lib/construct.rb', line 113

def each
  keys.each do |key|
    yield key, self[key]
  end
end

#include?(*args) ⇒ Boolean

Returns true if the construct has a value set for, or the schema defines, the key.

Returns:

  • (Boolean)


121
122
123
# File 'lib/construct.rb', line 121

def include?(*args)
  @data.include?(*args) or (@schema.include?(*args) and @schema[*args].include? :default)
end

#keysObject

Returns the keys, both set in the construct and specified in the schema.



126
127
128
# File 'lib/construct.rb', line 126

def keys
  @data.keys | @schema.keys
end

#load(yaml) ⇒ Object



130
131
132
133
134
135
136
# File 'lib/construct.rb', line 130

def load(yaml)
  data = YAML::load(yaml)

  data.each do |key, value|
    self[key] = value
  end
end

#to_hashObject

Flattens this construct (recursively) into a hash, merging schema with values. Useful for passing a Construct to a library that checks kind_of?



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/construct.rb', line 156

def to_hash
  inject({}) do |hash, pair|
    value = pair[1]
    hash[pair[0]] = case value
    when Construct
      value.to_hash
    else
      value
    end

    hash
  end
end

#to_yaml(opts = {}) ⇒ Object

Dumps the data (not the schema!) of this construct to YAML. Keys are expressed as strings.

This gets a little complicated.

If you define a schema where the default is a Construct

conf.define :sub, :default => Construct

and then try to write to it:

conf.sub.opt = 2

That opt gets stored on the schema sub. Everything works fine… except that when it comes time to serialize there’s now data buried in the schema tree. Therefore, we write out schema objects as well when they are non-empty.



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/construct.rb', line 187

def to_yaml(opts = {})
  hash = {}
  @schema.each do |key, value|
    if value[:default].kind_of? Construct
      hashed = YAML::load(value[:default].to_yaml)
      next if hashed.empty?
      hash[key.to_s] = hashed
    end
  end

  @data.each do |key, value|
    hash[key.to_s] = value
  end
  hash.to_yaml(opts)
end