sublime_dsl 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +136 -0
  3. data/Rakefile +248 -0
  4. data/SYNTAX.md +927 -0
  5. data/bin/subdsl +4 -0
  6. data/lib/sublime_dsl/cli/export.rb +134 -0
  7. data/lib/sublime_dsl/cli/import.rb +143 -0
  8. data/lib/sublime_dsl/cli.rb +125 -0
  9. data/lib/sublime_dsl/core_ext/enumerable.rb +24 -0
  10. data/lib/sublime_dsl/core_ext/string.rb +129 -0
  11. data/lib/sublime_dsl/core_ext.rb +4 -0
  12. data/lib/sublime_dsl/sublime_text/command.rb +157 -0
  13. data/lib/sublime_dsl/sublime_text/command_set.rb +112 -0
  14. data/lib/sublime_dsl/sublime_text/keyboard.rb +659 -0
  15. data/lib/sublime_dsl/sublime_text/keymap/dsl_reader.rb +194 -0
  16. data/lib/sublime_dsl/sublime_text/keymap.rb +385 -0
  17. data/lib/sublime_dsl/sublime_text/macro.rb +91 -0
  18. data/lib/sublime_dsl/sublime_text/menu.rb +237 -0
  19. data/lib/sublime_dsl/sublime_text/mouse.rb +149 -0
  20. data/lib/sublime_dsl/sublime_text/mousemap.rb +185 -0
  21. data/lib/sublime_dsl/sublime_text/package/dsl_reader.rb +91 -0
  22. data/lib/sublime_dsl/sublime_text/package/exporter.rb +138 -0
  23. data/lib/sublime_dsl/sublime_text/package/importer.rb +127 -0
  24. data/lib/sublime_dsl/sublime_text/package/reader.rb +102 -0
  25. data/lib/sublime_dsl/sublime_text/package/writer.rb +112 -0
  26. data/lib/sublime_dsl/sublime_text/package.rb +96 -0
  27. data/lib/sublime_dsl/sublime_text/setting_set.rb +123 -0
  28. data/lib/sublime_dsl/sublime_text.rb +48 -0
  29. data/lib/sublime_dsl/textmate/custom_base_name.rb +45 -0
  30. data/lib/sublime_dsl/textmate/grammar/dsl_reader.rb +383 -0
  31. data/lib/sublime_dsl/textmate/grammar/dsl_writer.rb +178 -0
  32. data/lib/sublime_dsl/textmate/grammar/plist_reader.rb +163 -0
  33. data/lib/sublime_dsl/textmate/grammar/plist_writer.rb +153 -0
  34. data/lib/sublime_dsl/textmate/grammar.rb +252 -0
  35. data/lib/sublime_dsl/textmate/plist.rb +141 -0
  36. data/lib/sublime_dsl/textmate/preference.rb +301 -0
  37. data/lib/sublime_dsl/textmate/snippet.rb +437 -0
  38. data/lib/sublime_dsl/textmate/theme/dsl_reader.rb +87 -0
  39. data/lib/sublime_dsl/textmate/theme/item.rb +74 -0
  40. data/lib/sublime_dsl/textmate/theme/plist_writer.rb +53 -0
  41. data/lib/sublime_dsl/textmate/theme.rb +364 -0
  42. data/lib/sublime_dsl/textmate.rb +9 -0
  43. data/lib/sublime_dsl/tools/blank_slate.rb +49 -0
  44. data/lib/sublime_dsl/tools/console.rb +74 -0
  45. data/lib/sublime_dsl/tools/helpers.rb +152 -0
  46. data/lib/sublime_dsl/tools/regexp_wannabe.rb +154 -0
  47. data/lib/sublime_dsl/tools/stable_inspect.rb +20 -0
  48. data/lib/sublime_dsl/tools/value_equality.rb +37 -0
  49. data/lib/sublime_dsl/tools/xml.rb +66 -0
  50. data/lib/sublime_dsl/tools.rb +66 -0
  51. data/lib/sublime_dsl.rb +23 -0
  52. metadata +145 -0
@@ -0,0 +1,138 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module SublimeText
5
+ class Package
6
+
7
+ ##
8
+ # An export operation with its options.
9
+
10
+ class Exporter
11
+
12
+ attr_reader :package
13
+ attr_accessor :root, :backup, :cleanup
14
+
15
+ def initialize(package = nil)
16
+ @package = package
17
+ @root = SublimeText.packages_dir
18
+ @backup = :always
19
+ @cleanup = false
20
+ end
21
+
22
+ def export(package = nil)
23
+ @package = package if package
24
+ validate
25
+ perform_backup
26
+ Console.info "exporting package to #{dir}"
27
+ Dir.mktmpdir('sublime_dsl_') do |tmpdir|
28
+ tmpdir = tmpdir.encode('utf-8') # for ruby 2.0
29
+ export_package tmpdir
30
+ copy_package tmpdir, dir
31
+ end
32
+ end
33
+
34
+ def dir
35
+ "#{root}/#{@package.name}"
36
+ end
37
+
38
+ def keyboard=(name)
39
+ @keyboard_name = name
40
+ end
41
+
42
+ def validate
43
+ File.directory?(root) or raise OptionError, "no such directory: #{root}"
44
+ @backup = backup.to_sym
45
+ unless [:never, :always, :once].include?(backup)
46
+ warn "invalid backup option #{backup.inspect}, 'always' assumed"
47
+ @backup = :always
48
+ end
49
+ end
50
+
51
+ def perform_backup
52
+ # no backup needed if not asked or no file to backup
53
+ return unless backup != :never && Dir["#{dir}/*"].length > 0
54
+ # don't do it if only one backup is requested, and there is already one
55
+ name = Tools.filename(package.name)
56
+ return if backup == :once && Dir["#{root}/#{name}.*.zip"].length > 0
57
+ # backup
58
+ time_stamp = Tools.filename(Time.now.strftime("%Y-%m-%d %H:%M:%S"))
59
+ backup_file = "#{root}/#{name}.#{time_stamp}.zip"
60
+ Tools.zip dir, backup_file
61
+ Console.info "#{dir} backed up as #{backup_file}"
62
+ end
63
+
64
+ def export_package(dir)
65
+ p = package
66
+ p.themes.each { |t| t.export dir }
67
+ p.grammars.each { |g| g.export dir }
68
+ p.preferences.each { |pr| pr.export dir }
69
+ p.snippets.each { |s| s.export dir }
70
+ p.setting_sets.each { |s| s.export dir }
71
+ p.macros.each { |m| m.export dir }
72
+ p.command_sets.each { |c| c.export dir }
73
+ p.menus.each { |m| m.export dir }
74
+ p.mousemaps.each { |m| m.export dir }
75
+ p.keymaps.each { |k| k.export dir }
76
+ p.other_files.each { |f| copy_file f, dir }
77
+ end
78
+
79
+ def copy_package(from, to)
80
+
81
+ # remember the files originally there
82
+ if File.directory?(to)
83
+ old_files = Dir["#{to}/**/*"]
84
+ else
85
+ Dir.mkdir to
86
+ old_files = []
87
+ end
88
+
89
+ # copy from the temporary directory
90
+ new_files = []
91
+ Dir["#{from}/**/*"].each do |fromfile|
92
+ tofile = fromfile.dup
93
+ tofile[from] = to
94
+ new_files << tofile
95
+ if File.directory?(fromfile)
96
+ Dir.mkdir tofile unless File.directory?(tofile)
97
+ else
98
+ FileUtils.cp fromfile, tofile
99
+ end
100
+ end
101
+
102
+ return unless cleanup
103
+
104
+ # on Windows, do not delete files that had just case changes
105
+ if Tools.os == :Windows
106
+ old_files = old_files.map(&:downcase)
107
+ new_files = new_files.map(&:downcase)
108
+ end
109
+
110
+ # delete files/directories that disappeared
111
+ (old_files - new_files).each do |f|
112
+ next unless File.exist?(f) # removing directories also remove files
113
+ if File.directory?(f)
114
+ FileUtils.rm_r [f]
115
+ else
116
+ FileUtils.rm f
117
+ end
118
+ end
119
+
120
+ end
121
+
122
+ def copy_file(infile, outdir)
123
+ if File.directory?(infile)
124
+ outpath = "#{outdir}/#{File.basename(infile)}"
125
+ Dir.mkdir outpath unless File.directory?(outpath)
126
+ Dir["#{infile}/*"].each { |f| copy_file f, outpath }
127
+ else
128
+ # Console.progress ...
129
+ FileUtils.cp infile, outdir
130
+ end
131
+ end
132
+
133
+ end
134
+
135
+
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,127 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module SublimeText
5
+ class Package
6
+
7
+ ##
8
+ # An import operation with its options.
9
+
10
+ class Importer
11
+
12
+ attr_reader :package_name
13
+ attr_accessor :root, :alt_root, :include, :exclude
14
+
15
+ def initialize(name)
16
+ @package_name = name
17
+ @root = SublimeText.packages_dir
18
+ @alt_root = nil
19
+ @include = nil
20
+ @exclude = '*.cache;*.pyc'
21
+ end
22
+
23
+ def dir
24
+ "#{root}/#{package_name}"
25
+ end
26
+
27
+ def package
28
+ @package ||= import
29
+ end
30
+
31
+ private
32
+
33
+ def validate
34
+ File.directory?(dir) or raise OptionError, "no such directory: #{dir}"
35
+ alt_root.nil? || File.directory?(alt_root) or
36
+ raise OptionError, "no such directory: #{alt_root}"
37
+ end
38
+
39
+ def files
40
+ @files ||= begin
41
+ files = Dir["#{dir}/*"]
42
+
43
+ if self.include
44
+ specs = self.include.split(';')
45
+ files.select! do |f|
46
+ f = File.basename(f)
47
+ specs.any? { |s| File.fnmatch(s, f) }
48
+ end
49
+ end
50
+
51
+ if self.exclude
52
+ specs = self.exclude.split(';')
53
+ files.reject! do |f|
54
+ f = File.basename(f)
55
+ specs.any? { |s| File.fnmatch(s, f) }
56
+ end
57
+ end
58
+
59
+ files
60
+ end
61
+ end
62
+
63
+ def import
64
+ validate
65
+
66
+ if files.empty?
67
+ Console.info "#{dir}: nothing to import"
68
+ return nil
69
+ end
70
+
71
+ Console.info "importing package from #{dir}"
72
+ package = Package.new(package_name)
73
+ files.each.with_index(1) do |file, index|
74
+ file = selected_file(file)
75
+ Console.progress index, files.length, file
76
+ begin
77
+ case File.extname(file).downcase
78
+ when '.tmtheme'
79
+ package.themes << TextMate::Theme.import(file)
80
+ when '.tmlanguage'
81
+ package.grammars << TextMate::Grammar.import(file)
82
+ when '.tmpreferences'
83
+ package.preferences << TextMate::Preference.import(file)
84
+ when '.tmsnippet', '.sublime-snippet'
85
+ package.snippets << TextMate::Snippet.import(file)
86
+ when '.sublime-settings'
87
+ package.setting_sets << SettingSet.import(file)
88
+ when '.sublime-macro'
89
+ package.macros << Macro.import(file)
90
+ when '.sublime-commands'
91
+ package.command_sets << CommandSet.import(file)
92
+ when '.sublime-menu'
93
+ package.menus << Menu.import(file)
94
+ when '.sublime-mousemap'
95
+ package.mousemaps << MouseMap.import(file)
96
+ when '.sublime-keymap'
97
+ package.keymaps << KeyMap.import(file)
98
+
99
+ when '.sublime-build'
100
+ # not worth it
101
+ package.other_files << file
102
+ when '.sublime-completions'
103
+ # only HTML & PHP, not surviving in ST 3
104
+ package.other_files << file
105
+ else
106
+ package.other_files << file
107
+ end
108
+ rescue => ex
109
+ Console.error "error when importing #{file}:"
110
+ raise ex
111
+ end
112
+ end
113
+
114
+ package
115
+ end
116
+
117
+ def selected_file(file)
118
+ return file unless alt_root
119
+ alt_file = file.sub(root, alt_root)
120
+ File.exist?(alt_file) ? alt_file : file
121
+ end
122
+
123
+ end
124
+
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module SublimeText
5
+ class Package
6
+
7
+ ##
8
+ # A DSL read operation with its options.
9
+
10
+ class Reader
11
+
12
+ attr_reader :package_name
13
+ attr_accessor :root, :alt_root, :include, :exclude
14
+
15
+ def initialize(name)
16
+ @package_name = name
17
+ @root = '.'
18
+ @alt_root = nil
19
+ @include = nil
20
+ @exclude = '*.keyboard.rb'
21
+ end
22
+
23
+ def dir
24
+ "#{root}/#{package_name}"
25
+ end
26
+
27
+ def package
28
+ @package ||= read
29
+ end
30
+
31
+ private
32
+
33
+ def validate
34
+ File.directory?(dir) or raise OptionError, "no such directory: #{dir}"
35
+ alt_root.nil? || File.directory?(alt_root) or
36
+ raise OptionError, "no such directory: #{alt_root}"
37
+ end
38
+
39
+ def files
40
+ @files ||= begin
41
+ files = Dir["#{dir}/*"]
42
+
43
+ if self.include
44
+ specs = self.include.split(';')
45
+ files.select! do |f|
46
+ f = File.basename(f)
47
+ specs.any? { |s| File.fnmatch(s, f) }
48
+ end
49
+ end
50
+
51
+ if self.exclude
52
+ specs = self.exclude.split(';')
53
+ files.reject! do |f|
54
+ f = File.basename(f)
55
+ specs.any? { |s| File.fnmatch(s, f) }
56
+ end
57
+ end
58
+
59
+ files
60
+ end
61
+ end
62
+
63
+ def read
64
+ validate
65
+
66
+ if files.empty?
67
+ Console.info "#{dir}: nothing to read"
68
+ return nil
69
+ end
70
+
71
+ Console.info "processing DSL files in #{dir}"
72
+ package = Package.new(package_name)
73
+
74
+ files.each.with_index(1) do |file, index|
75
+ file = selected_file(file)
76
+ Console.progress index, files.length, file
77
+ begin
78
+ if File.basename(file) =~ /\.rb$/
79
+ DSLReader.new file, package
80
+ else
81
+ package.other_files << file
82
+ end
83
+ rescue => ex
84
+ Console.error "error when reading #{file}:"
85
+ raise ex
86
+ end
87
+ end
88
+
89
+ package
90
+ end
91
+
92
+ def selected_file(file)
93
+ return file unless alt_root
94
+ alt_file = file.sub(root, alt_root)
95
+ File.exist?(alt_file) ? alt_file : file
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,112 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module SublimeText
5
+ class Package
6
+
7
+ ##
8
+ # A DSL writing operation with its options.
9
+
10
+ class Writer
11
+
12
+ attr_reader :package
13
+ attr_accessor :root, :backup, :copy_other_files
14
+
15
+ def initialize(package = nil)
16
+ @package = package
17
+ @root = '.'
18
+ @backup = :once
19
+ @copy_other_files = true
20
+ @keyboard_name = nil
21
+ end
22
+
23
+ def write(package = nil)
24
+ @package = package if package
25
+ validate
26
+ perform_backup
27
+ FileUtils.rm_r Dir["#{dir}/*"] if File.directory?(dir)
28
+ Console.info "writing package DSL to #{dir}"
29
+ write_package
30
+ end
31
+
32
+ def dir
33
+ "#{root}/#{@package.name}"
34
+ end
35
+
36
+ def keyboard=(name)
37
+ @keyboard_name = name
38
+ end
39
+
40
+ def keyboard
41
+ @keyboard ||=
42
+ if @keyboard_name.nil? || @keyboard_name == Keyboard.sublime.name
43
+ Keyboard.sublime
44
+ else
45
+ Keyboard.get(@keyboard_name, root)
46
+ end
47
+ end
48
+
49
+ def validate
50
+ File.directory?(root) or raise OptionError, "no such directory: #{root}"
51
+ @backup = backup.to_sym
52
+ unless [:never, :always, :once].include?(backup)
53
+ warn "invalid backup option #{backup.inspect}, 'always' assumed"
54
+ @backup = :always
55
+ end
56
+ keyboard
57
+ end
58
+
59
+ def perform_backup
60
+ # no backup needed if not asked or no file to backup
61
+ return unless backup != :never && Dir["#{dir}/*"].length > 0
62
+ # don't do it if only one backup is requested, and there is already one
63
+ name = Tools.filename(package.name)
64
+ return if backup == :once && Dir["#{root}/#{name}.*.zip"].length > 0
65
+ # backup
66
+ time_stamp = Tools.filename(Time.now.strftime("%Y-%m-%d %H:%M:%S"))
67
+ backup_file = "#{root}/#{name}.#{time_stamp}.zip"
68
+ Tools.zip dir, backup_file
69
+ Console.info "#{dir} backed up as #{backup_file}"
70
+ end
71
+
72
+ def write_package
73
+ Dir.mkdir dir unless File.directory?(dir)
74
+ p = package
75
+ p.themes.each { |t| t.write dir }
76
+ p.grammars.each { |g| g.write dir }
77
+ write_all p.preferences, "#{dir}/preferences.rb"
78
+ TextMate::Snippet::DSLWriter.new(p.snippets).write "#{dir}/snippets.rb"
79
+ write_all p.setting_sets, "#{dir}/settings.rb"
80
+ write_all p.macros.sort_by(&:name), "#{dir}/macros.rb"
81
+ write_all p.command_sets, "#{dir}/commands.rb"
82
+ p.menus.each { |m| m.write dir }
83
+ p.mousemaps.each { |m| m.write dir }
84
+ p.keymaps.each { |k| k.for_keyboard(keyboard).write dir }
85
+ return unless copy_other_files
86
+ p.other_files.each { |f| copy_file f, dir }
87
+ end
88
+
89
+ def copy_file(infile, outdir)
90
+ if File.directory?(infile)
91
+ outpath = "#{outdir}/#{File.basename(infile)}"
92
+ Dir.mkdir outpath unless File.directory?(outpath)
93
+ Dir["#{infile}/*"].each { |f| copy_file f, outpath }
94
+ else
95
+ # Console.progress ...
96
+ FileUtils.cp infile, outdir
97
+ end
98
+ end
99
+
100
+ def write_all(collection, file)
101
+ return if collection.empty?
102
+ File.open(file, 'wb:utf-8') do |f|
103
+ f.puts '# encoding: utf-8'
104
+ collection.each { |e| f.puts "\n#{e.to_dsl}" }
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+
3
+ require_relative 'package/importer'
4
+ require_relative 'package/writer'
5
+ require_relative 'package/reader'
6
+ require_relative 'package/exporter'
7
+ require_relative 'package/dsl_reader'
8
+
9
+ module SublimeDSL
10
+ module SublimeText
11
+
12
+ class Package
13
+
14
+ # Create a new Package from a Sublime Text 'Packages' subdirectory.
15
+ #
16
+ # The options can be any Importer option.
17
+
18
+ def self.import(name, options = {})
19
+ importer = Importer.new(name)
20
+ process_options importer, options
21
+
22
+ importer.package
23
+ end
24
+
25
+ # Create a new Package from a DSL directory.
26
+ #
27
+ # The options can be any Reader option.
28
+
29
+ def self.read(name, options = {})
30
+ reader = Reader.new(name)
31
+ process_options reader, options
32
+
33
+ reader.package
34
+ end
35
+
36
+ def self.process_options(object, options)
37
+ options.each_pair do |k,v|
38
+ m = "#{k}="
39
+ raise OptionError, "invalid option '#{k}'" unless object.respond_to? m
40
+ object.send m, v
41
+ end
42
+ end
43
+
44
+ attr_reader :name
45
+
46
+ attr_reader :themes
47
+ attr_reader :grammars
48
+ attr_reader :preferences
49
+ attr_reader :snippets
50
+ attr_reader :setting_sets
51
+ attr_reader :macros
52
+ attr_reader :command_sets
53
+ attr_reader :menus
54
+ attr_reader :mousemaps
55
+ attr_reader :keymaps
56
+
57
+ attr_reader :other_files
58
+
59
+ def initialize(name)
60
+ @name = name
61
+ @themes = []
62
+ @grammars = []
63
+ @preferences = []
64
+ @snippets = []
65
+ @setting_sets = []
66
+ @macros = []
67
+ @command_sets = []
68
+ @menus = []
69
+ @mousemaps = []
70
+ @keymaps = []
71
+
72
+ @other_files = []
73
+ end
74
+
75
+ # Writes the package to DSL files.
76
+ # The options can be any Writer option.
77
+
78
+ def write(options = {})
79
+ writer = Writer.new(self)
80
+ self.class.process_options writer, options
81
+ writer.write
82
+ end
83
+
84
+ # Exports the package.
85
+ # The options can be any Exporter option.
86
+
87
+ def export(options = {})
88
+ exporter = Exporter.new(self)
89
+ self.class.process_options exporter, options
90
+ exporter.export
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,123 @@
1
+ # encoding: utf-8
2
+
3
+ module SublimeDSL
4
+ module SublimeText
5
+
6
+ class SettingSet
7
+
8
+ def self.import(file)
9
+ name = File.basename(file, File.extname(file))
10
+ set = new(name)
11
+ json = File.read(file, encoding: 'utf-8')
12
+ h = json.empty? ? {} : JSON[json]
13
+ begin
14
+ h.each_pair { |key, value| set.settings << Setting.new(key, value) }
15
+ rescue => ex
16
+ puts "file: #{file}"
17
+ raise ex
18
+ end
19
+
20
+ set
21
+ end
22
+
23
+ attr_reader :name, :settings
24
+
25
+ def initialize(name)
26
+ @name = name
27
+ @settings = []
28
+ end
29
+
30
+ def to_dsl
31
+ dsl = "settings #{name.to_source} do\n"
32
+ dsl << "\n" unless settings.empty?
33
+ settings.each { |s| dsl << " #{s.to_dsl}\n" }
34
+ dsl << "\nend"
35
+ end
36
+
37
+ def export(dir)
38
+ file = "#{dir}/#{name}.sublime-settings"
39
+ File.open(file, 'wb:utf-8') { |f| f.write to_json }
40
+ end
41
+
42
+ def to_json
43
+ h = Hash[settings.map { |s| [s.key, s.value] }]
44
+ JSON.pretty_generate(h)
45
+ end
46
+
47
+ class Setting
48
+
49
+ attr_reader :key, :value
50
+
51
+ def initialize(key, value)
52
+ @key = key
53
+ @value = value
54
+ end
55
+
56
+ def to_dsl
57
+ "#{key} #{display(value)}"
58
+ end
59
+
60
+ private
61
+
62
+ def display(v)
63
+ case v
64
+ when String
65
+ v.to_source
66
+ when TrueClass, FalseClass, Numeric
67
+ v.to_s
68
+ when Array
69
+ display_array(v)
70
+ when Hash
71
+ v.empty? ? 'Hash.new' : v.inspect[1..-2] # remove braces
72
+ when NilClass
73
+ 'nil'
74
+ else
75
+ raise Error, "value: #{v.inspect} for #{name.inspect}"
76
+ end
77
+ end
78
+
79
+ def display_array(a)
80
+ if a.empty?
81
+ '[]'
82
+ elsif a.all? { |e| e.is_a?(String) && e =~ /\A(\S+)\z/ }
83
+ "%w(#{a.join(' ')})"
84
+ else
85
+ a.inspect
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ class DSLReader < Tools::BlankSlate
92
+
93
+ def initialize(file = nil)
94
+ @setting_sets = []
95
+ @current_set = nil
96
+ instance_eval File.read(file, encoding: 'utf-8'), file if file
97
+ end
98
+
99
+ def _setting_sets
100
+ @setting_sets
101
+ end
102
+
103
+ def method_missing(sym, *args, &block)
104
+ @current_set or raise Error, "'#{sym}' is invalid outside of a 'settings' block"
105
+ args.empty? and raise Error, "no value for setting '#{sym}'"
106
+ args.length > 1 and raise Error, "more than one value for setting '#{sym}'"
107
+ @current_set.settings << Setting.new(sym.to_s, args.first)
108
+ end
109
+
110
+ def settings(name, &block)
111
+ @current_set and raise Error, "'settings' blocks cannot be nested"
112
+ @current_set = SettingSet.new(name)
113
+ instance_eval(&block)
114
+ @setting_sets << @current_set
115
+ @current_set = nil
116
+ end
117
+
118
+ end
119
+
120
+ end
121
+
122
+ end
123
+ end