caesars 0.5.6 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (5) hide show
  1. data/CHANGES.txt +9 -2
  2. data/README.rdoc +4 -4
  3. data/caesars.gemspec +8 -8
  4. data/lib/caesars.rb +147 -44
  5. metadata +5 -6
data/CHANGES.txt CHANGED
@@ -1,12 +1,19 @@
1
- CAESAR -- CHANGES
1
+ CAESARS -- CHANGES
2
2
 
3
3
 
4
+ #### 0.6.0 (2009-04-30) ###############################
5
+
6
+ * ADDED: Forced reloading for Caesars::Config.refresh. This allows
7
+ one DSL to affect the parsing of another.
8
+ * ADDED: Caesars.forced_ignore
9
+ * ADDED: Better docs for Caesars::Config
10
+ * ADDED: Ceasars::Config.has_key?
11
+
4
12
 
5
13
  #### 0.5.6 (2009-04-28) ###############################
6
14
 
7
15
  * FIXED: Bug in fixed_hash which wasn't forcing the hash man!
8
16
 
9
-
10
17
  #### 0.5.5 (2009-04-27) ###############################
11
18
 
12
19
  * CHANGE: Caesars.chill and Caesars.forced_hash can now be used together.
data/README.rdoc CHANGED
@@ -1,4 +1,4 @@
1
- = Caesars - v0.5
1
+ = Caesars - v0.6
2
2
 
3
3
  A simple class for rapid DSL prototyping in Ruby.
4
4
 
@@ -15,7 +15,7 @@ Or for GitHub fans:
15
15
  * git clone git://github.com/delano/caesar.git
16
16
 
17
17
 
18
- == EXAMPLE 1 -- Flavour
18
+ == EXAMPLE 1 -- A Simple DSL
19
19
 
20
20
  require 'caesars'
21
21
 
@@ -38,7 +38,7 @@ Or for GitHub fans:
38
38
 
39
39
 
40
40
 
41
- == EXAMPLE 2 -- Staff
41
+ == EXAMPLE 2 -- Storing Blocks as Procs
42
42
 
43
43
  require 'caesars'
44
44
 
@@ -111,7 +111,7 @@ Or for GitHub fans:
111
111
  p @staff_fte.splashdown.sheila.salary.call(rand(100)) # => 549.77
112
112
 
113
113
 
114
- == EXAMPLE 3 -- External Config file
114
+ == EXAMPLE 3 -- External Config Files
115
115
 
116
116
  require 'caesars'
117
117
 
data/caesars.gemspec CHANGED
@@ -1,13 +1,14 @@
1
1
  @spec = Gem::Specification.new do |s|
2
- s.name = %q{caesars}
3
- s.version = "0.5.6"
4
- s.date = %q{2009-04-28}
2
+ s.name = "caesars"
3
+ s.rubyforge_project = "caesars"
4
+ s.version = "0.6.0"
5
+ s.date = "2009-04-30"
5
6
  s.specification_version = 1 if s.respond_to? :specification_version=
6
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
7
8
 
8
9
  s.authors = ["Delano Mandelbaum"]
9
- s.description = %q{A simple class for rapid DSL prototyping in Ruby.}
10
- s.summary = %q{Caesars: A simple class for rapid DSL prototyping in Ruby.}
10
+ s.description = %q{Rapid DSL prototyping in Ruby.}
11
+ s.summary = %q{Caesars: Rapid DSL prototyping in Ruby.}
11
12
  s.email = %q{delano@solutious.com}
12
13
 
13
14
  # = MANIFEST =
@@ -29,8 +30,7 @@
29
30
  s.has_rdoc = true
30
31
  s.homepage = %q{http://github.com/delano/caesar}
31
32
  s.extra_rdoc_files = %w[README.rdoc LICENSE.txt CHANGES.txt]
32
- s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Caesars: A simple class for rapid DSL prototyping in Ruby.", "--main", "README.rdoc"]
33
+ s.rdoc_options = ["--line-numbers", "--title", "Caesars: Rapid DSL prototyping in Ruby.", "--main", "README.rdoc"]
33
34
  s.require_paths = ["lib"]
34
- s.rubygems_version = %q{1.1.1}
35
- s.rubyforge_project = "caesars"
35
+ s.rubygems_version = "1.1.1"
36
36
  end
data/lib/caesars.rb CHANGED
@@ -1,5 +1,5 @@
1
1
 
2
- # Caesars -- A simple class for rapid DSL prototyping.
2
+ # Caesars -- Rapid DSL prototyping in Ruby.
3
3
  #
4
4
  # Subclass Caesars and start drinking! I mean, start prototyping
5
5
  # your own domain specific language!
@@ -7,10 +7,11 @@
7
7
  # See bin/example
8
8
  #
9
9
  class Caesars
10
- VERSION = 0.5
10
+ VERSION = 0.6
11
11
  @@debug = false
12
12
  @@chilled = {}
13
13
  @@forced_array = {}
14
+ @@forced_ignore = {}
14
15
 
15
16
  def Caesars.enable_debug; @@debug = true; end
16
17
  def Caesars.disable_debug; @@debug = false; end
@@ -27,7 +28,12 @@ class Caesars
27
28
  return false unless name
28
29
  @@forced_array.has_key?(name.to_sym)
29
30
  end
30
-
31
+ # Is the given +name+ a forced ignore?
32
+ # See Caesars.forced_ignore
33
+ def Caesars.forced_ignore?(name)
34
+ return false unless name
35
+ @@forced_ignore.has_key?(name.to_sym)
36
+ end
31
37
 
32
38
  # A subclass of ::Hash that provides method names for hash parameters.
33
39
  # It's like a lightweight OpenStruct.
@@ -70,6 +76,7 @@ class Caesars
70
76
  @caesars_name = name if name
71
77
  @caesars_properties = Caesars::Hash.new
72
78
  @caesars_pointer = @caesars_properties
79
+ init if respond_to?(:init)
73
80
  end
74
81
 
75
82
  # Returns an array of the available
@@ -231,6 +238,10 @@ class Caesars
231
238
  # previously) and also in subclasses of Caesars for returning the appropriate
232
239
  # attribute values.
233
240
  def method_missing(meth, *args, &b)
241
+ if Caesars.forced_ignore?(meth)
242
+ STDERR.puts "Forced ignore: #{meth}" if Caesars.debug?
243
+ return
244
+ end
234
245
 
235
246
  # Handle the setter, attribute=
236
247
  if meth.to_s =~ /=$/ && @caesars_properties.has_key?(meth.to_s.chop.to_sym)
@@ -314,7 +325,11 @@ class Caesars
314
325
  module_eval %Q{
315
326
  def #{caesars_meth}(*caesars_names,&b)
316
327
  this_meth = :'#{caesars_meth}'
317
-
328
+ if Caesars.forced_ignore?(this_meth)
329
+ STDERR.puts "Forced ignore: \#{this_meth}" if Caesars.debug?
330
+ return
331
+ end
332
+
318
333
  if @caesars_properties.has_key?(this_meth) && caesars_names.empty? && b.nil?
319
334
  return @caesars_properties[this_meth]
320
335
  end
@@ -362,7 +377,7 @@ class Caesars
362
377
  # end
363
378
  # end
364
379
  #
365
- # @config.food.count.call(3) # => 5
380
+ # @food.count.call(3) # => 5
366
381
  #
367
382
  def self.chill(caesars_meth)
368
383
  @@chilled[caesars_meth.to_sym] = true
@@ -378,15 +393,37 @@ class Caesars
378
393
  #
379
394
  # food do
380
395
  # taste :delicious
381
- # sauce
396
+ # sauce :tabasco, :worcester
397
+ # sauce :franks
382
398
  # end
383
399
  #
384
- # @config.food.count.call(3) # => 5
400
+ # @food.sauce # => [[:tabasco, :worcester], [:franks]]
401
+ #
385
402
  def self.forced_array(caesars_meth)
403
+ STDERR.puts "forced_array: #{caesars_meth}" if Caesars.debug?
386
404
  @@forced_array[caesars_meth.to_sym] = true
387
405
  nil
388
406
  end
389
407
 
408
+ # Specify a method that should always be ignored.
409
+ # Here's an example:
410
+ #
411
+ # class Food < Caesars
412
+ # forced_ignore :taste
413
+ # end
414
+ #
415
+ # food do
416
+ # taste :delicious
417
+ # end
418
+ #
419
+ # @food.taste # => nil
420
+ #
421
+ def self.forced_ignore(caesars_meth)
422
+ STDERR.puts "forced_ignore: #{caesars_meth}" if Caesars.debug?
423
+ @@forced_ignore[caesars_meth.to_sym] = true
424
+ nil
425
+ end
426
+
390
427
  # Executes automatically when Caesars is subclassed. This creates the
391
428
  # YourClass::DSL module which contains a single method named after YourClass
392
429
  # that is used to catch the top level DSL method.
@@ -399,7 +436,8 @@ class Caesars
399
436
  # end
400
437
  #
401
438
  def self.inherited(modname)
402
- meth = (modname.to_s.split(/::/))[-1].downcase # Some::ClassName => classname
439
+ # NOTE: We may be able to replace this without an eval using Module.nesting
440
+ meth = (modname.to_s.split(/::/))[-1].downcase # Some::HighBall => highball
403
441
  module_eval %Q{
404
442
  module #{modname}::DSL
405
443
  def #{meth}(*args, &b)
@@ -448,6 +486,16 @@ class Caesars::Config
448
486
 
449
487
  @@glasses = []
450
488
 
489
+ # Used by postprocess to tell refresh to reload all configs.
490
+ class ForceRefresh < RuntimeError
491
+ # The list of config types that need to be refreshed. This is currently
492
+ # for informational-purposes only. It does not affect which files/config
493
+ # types are refreshed. See Caesars::Config::ForcedRefresh
494
+ attr_reader :glasses
495
+ def initialize(*glasses); @glasses = glasses; end
496
+ def message; "Force refresh of: #{@glasses.join(',')}"; end
497
+ end
498
+
451
499
  # +args+ is a last of config file paths to load into this instance.
452
500
  # If the last argument is a hash, it's assumed to be a list of
453
501
  # options. The available options are:
@@ -459,62 +507,80 @@ class Caesars::Config
459
507
  @options = args.last.is_a?(Hash) ? args.pop : {}
460
508
  @paths = args.empty? ? [] : args
461
509
  @options = {}
462
-
510
+ @forced_refreshes = 0
463
511
  refresh
464
512
  end
465
513
 
514
+ # Reset all config instance variables to nil.
466
515
  def caesars_init
467
516
  # Remove instance variables used to populate DSL data
468
- instance_variables.each do |varname|
469
- next if varname == :'@options' || varname == :'@paths' # Ruby 1.9.1
470
- next if varname == '@options' || varname == '@paths' # Ruby 1.8
471
- instance_variable_set(varname, nil)
472
- end
473
-
517
+ keys.each { |confname| instance_variable_set("@#{confname}", nil) }
474
518
  # Re-apply options
475
519
  @options.each_pair do |n,v|
476
520
  self.send("#{n}=", v) if respond_to?("#{n}=")
477
521
  end
478
-
479
522
  check_paths # make sure files exist
480
523
  end
481
524
 
482
- # This method is a stub. It gets called by refresh after the
483
- # config file has be loaded. You can use it to do some post
525
+ # This method is a stub. It gets called by refresh after each
526
+ # config file has be loaded. You can use it to run file specific
484
527
  # processing on the configuration before it's used elsewhere.
485
528
  def postprocess
486
529
  end
487
530
 
531
+ # Clear all current configuration (sets all config instance
532
+ # variables to nil) and reload all config files in +@paths+.
533
+ # After each path is loaded, Caesars::Config.postprocess is
534
+ # called. If a ForceRefresh exception is raise, refresh is
535
+ # run again from the start. This is useful in the case
536
+ # where one DSL can affect the parsing of another. Note that
537
+ # refresh only clears the instance variables, the class vars
538
+ # for each of the DSLs are not affected so all calls to
539
+ # +forced_array+, +forced_hash+, +chill+ and +forced_ignore+
540
+ # are unaffected.
541
+ #
542
+ # Rudy has an example of forced refreshing in action. See
543
+ # the files (http://github.com/solutious/rudy):
544
+ #
545
+ # * +lib/rudy/config.rb+
546
+ # * +lib/rudy/config/objects.rb+.
547
+ #
488
548
  def refresh
489
- caesars_init
490
-
491
- @paths.each do |path|
492
- puts "Loading config from #{path}" if @verbose
493
-
494
- begin
495
- @@glasses.each { |glass| extend glass }
549
+ caesars_init # Delete all current configuration
550
+ @@glasses.each { |glass| extend glass }
551
+
552
+ begin
553
+ @paths.each do |path|
554
+ puts "Loading config from #{path}" if @verbose || Caesars.debug?
496
555
  dsl = File.read path
497
-
498
- # We're using eval so the DSL code can be executed in this
499
- # namespace.
500
- eval %Q{
501
- #{dsl}
502
- }, binding, __FILE__, __LINE__
503
-
504
- postprocess
505
-
506
- rescue Caesars::Error => ex
507
- STDERR.puts ex.message
508
- STDERR.puts ex.backtrace if Caesars.debug?
509
- rescue ArgumentError, SyntaxError => ex
510
- STDERR.puts "Syntax error in #{path}."
511
- STDERR.puts ex.message
512
- STDERR.puts ex.backtrace if Caesars.debug?
513
- exit 1
556
+ # eval so the DSL code can be executed in this namespace.
557
+ eval dsl, binding, __FILE__, __LINE__
558
+ # Execute Caesars::Config.postprocesses every time a file is loaded
559
+ postprocess # Can raise ForceRefresh
560
+ end
561
+
562
+ rescue Caesars::Config::ForceRefresh => ex
563
+ @forced_refreshes += 1
564
+ if @forced_refreshes > 3
565
+ STDERR.puts "Too many forced refreshed (#{@forced_refreshes})"
566
+ exit 9
514
567
  end
568
+ STDERR.puts ex.message if Caesars.debug?
569
+ refresh
570
+
571
+ rescue Caesars::Error => ex
572
+ STDERR.puts ex.message
573
+ STDERR.puts ex.backtrace if Caesars.debug?
574
+ rescue ArgumentError, SyntaxError => ex
575
+ STDERR.puts "Syntax error in #{path}."
576
+ STDERR.puts ex.message
577
+ STDERR.puts ex.backtrace if Caesars.debug?
578
+ exit 1
515
579
  end
516
580
  end
517
581
 
582
+ # Checks all values of +@paths+, raises an exception for nil
583
+ # values and file paths that don't exist.
518
584
  def check_paths
519
585
  @paths.each do |path|
520
586
  raise "You provided a nil value" unless path
@@ -522,7 +588,7 @@ class Caesars::Config
522
588
  end
523
589
  end
524
590
 
525
-
591
+ # Do any of the known DSLs have config data?
526
592
  def empty?
527
593
  keys.each do |obj|
528
594
  return false if self.respond_to?(obj.to_sym)
@@ -530,18 +596,55 @@ class Caesars::Config
530
596
  true
531
597
  end
532
598
 
599
+ # Specify a DSL class (+glass+) to include in this config.
600
+ #
601
+ # class CoolDrink < Caesars::Config
602
+ # dsl CoolDrink::Flavours::DSL
603
+ # end
604
+ #
533
605
  def self.dsl(glass)
534
606
  @@glasses << glass
535
607
  end
536
608
 
609
+ # Provide a hash-like interface for Config classes.
610
+ # +name+ is the name of a DSL config.
611
+ #
612
+ # class CoolDrink < Caesars::Config
613
+ # dsl CoolDrink::Flavours::DSL
614
+ # end
615
+ #
616
+ # cd = CoolDrink.new('/path/2/config')
617
+ # cd[:flavours] # => {}
618
+ #
537
619
  def [](name)
538
620
  self.send(name) if respond_to?(name)
539
621
  end
540
-
622
+
623
+ # Returns the list of known DSL config names.
624
+ # class CoolDrink < Caesars::Config
625
+ # dsl CoolDrink::Flavours::DSL
626
+ # end
627
+ #
628
+ # cd = CoolDrink.new('/path/2/config')
629
+ # cd.keys # => [:flavours]
630
+ #
541
631
  def keys
542
632
  @@glasses.collect { |glass| glass.methname }
543
633
  end
544
634
 
635
+ # Is +name+ a known configuration type?
636
+ #
637
+ # class CoolDrink < Caesars::Config
638
+ # dsl CoolDrink::Flavours::DSL
639
+ # end
640
+ #
641
+ # cd = CoolDrink.new('/path/2/config')
642
+ # cd.has_key?(:taste) # => false
643
+ # cd.has_key?(:flavours) # => true
644
+ #
645
+ def has_key?(name)
646
+ respond_to?(name)
647
+ end
545
648
  end
546
649
 
547
650
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: caesars
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,11 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-04-28 00:00:00 -04:00
12
+ date: 2009-04-30 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
16
- description: A simple class for rapid DSL prototyping in Ruby.
16
+ description: Rapid DSL prototyping in Ruby.
17
17
  email: delano@solutious.com
18
18
  executables: []
19
19
 
@@ -40,9 +40,8 @@ licenses: []
40
40
  post_install_message:
41
41
  rdoc_options:
42
42
  - --line-numbers
43
- - --inline-source
44
43
  - --title
45
- - "Caesars: A simple class for rapid DSL prototyping in Ruby."
44
+ - "Caesars: Rapid DSL prototyping in Ruby."
46
45
  - --main
47
46
  - README.rdoc
48
47
  require_paths:
@@ -65,6 +64,6 @@ rubyforge_project: caesars
65
64
  rubygems_version: 1.3.2
66
65
  signing_key:
67
66
  specification_version: 1
68
- summary: "Caesars: A simple class for rapid DSL prototyping in Ruby."
67
+ summary: "Caesars: Rapid DSL prototyping in Ruby."
69
68
  test_files: []
70
69