automateit 0.70923

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. data.tar.gz.sig +1 -0
  2. data/CHANGES.txt +100 -0
  3. data/Hoe.rake +35 -0
  4. data/Manifest.txt +111 -0
  5. data/README.txt +44 -0
  6. data/Rakefile +284 -0
  7. data/TESTING.txt +57 -0
  8. data/TODO.txt +26 -0
  9. data/TUTORIAL.txt +390 -0
  10. data/bin/ai +3 -0
  11. data/bin/aifield +82 -0
  12. data/bin/aitag +128 -0
  13. data/bin/automateit +117 -0
  14. data/docs/friendly_errors.txt +50 -0
  15. data/docs/previews.txt +86 -0
  16. data/env.sh +4 -0
  17. data/examples/basic/Rakefile +26 -0
  18. data/examples/basic/config/automateit_env.rb +16 -0
  19. data/examples/basic/config/fields.yml +3 -0
  20. data/examples/basic/config/tags.yml +13 -0
  21. data/examples/basic/dist/README.txt +9 -0
  22. data/examples/basic/dist/myapp_server.erb +30 -0
  23. data/examples/basic/install.log +15 -0
  24. data/examples/basic/lib/README.txt +10 -0
  25. data/examples/basic/recipes/README.txt +4 -0
  26. data/examples/basic/recipes/install.rb +53 -0
  27. data/examples/basic/recipes/uninstall.rb +6 -0
  28. data/gpl.txt +674 -0
  29. data/lib/automateit.rb +66 -0
  30. data/lib/automateit/account_manager.rb +106 -0
  31. data/lib/automateit/account_manager/linux.rb +171 -0
  32. data/lib/automateit/account_manager/passwd.rb +69 -0
  33. data/lib/automateit/account_manager/portable.rb +136 -0
  34. data/lib/automateit/address_manager.rb +165 -0
  35. data/lib/automateit/address_manager/linux.rb +80 -0
  36. data/lib/automateit/address_manager/portable.rb +37 -0
  37. data/lib/automateit/cli.rb +80 -0
  38. data/lib/automateit/common.rb +65 -0
  39. data/lib/automateit/constants.rb +33 -0
  40. data/lib/automateit/edit_manager.rb +292 -0
  41. data/lib/automateit/error.rb +10 -0
  42. data/lib/automateit/field_manager.rb +103 -0
  43. data/lib/automateit/interpreter.rb +641 -0
  44. data/lib/automateit/package_manager.rb +242 -0
  45. data/lib/automateit/package_manager/apt.rb +63 -0
  46. data/lib/automateit/package_manager/egg.rb +64 -0
  47. data/lib/automateit/package_manager/gem.rb +179 -0
  48. data/lib/automateit/package_manager/portage.rb +69 -0
  49. data/lib/automateit/package_manager/yum.rb +65 -0
  50. data/lib/automateit/platform_manager.rb +47 -0
  51. data/lib/automateit/platform_manager/darwin.rb +30 -0
  52. data/lib/automateit/platform_manager/debian.rb +26 -0
  53. data/lib/automateit/platform_manager/freebsd.rb +25 -0
  54. data/lib/automateit/platform_manager/gentoo.rb +26 -0
  55. data/lib/automateit/platform_manager/lsb.rb +40 -0
  56. data/lib/automateit/platform_manager/struct.rb +78 -0
  57. data/lib/automateit/platform_manager/uname.rb +29 -0
  58. data/lib/automateit/platform_manager/windows.rb +33 -0
  59. data/lib/automateit/plugin.rb +7 -0
  60. data/lib/automateit/plugin/base.rb +32 -0
  61. data/lib/automateit/plugin/driver.rb +218 -0
  62. data/lib/automateit/plugin/manager.rb +232 -0
  63. data/lib/automateit/project.rb +460 -0
  64. data/lib/automateit/root.rb +14 -0
  65. data/lib/automateit/service_manager.rb +79 -0
  66. data/lib/automateit/service_manager/chkconfig.rb +39 -0
  67. data/lib/automateit/service_manager/rc_update.rb +37 -0
  68. data/lib/automateit/service_manager/sysv.rb +126 -0
  69. data/lib/automateit/service_manager/update_rcd.rb +35 -0
  70. data/lib/automateit/shell_manager.rb +261 -0
  71. data/lib/automateit/shell_manager/base_link.rb +67 -0
  72. data/lib/automateit/shell_manager/link.rb +24 -0
  73. data/lib/automateit/shell_manager/portable.rb +421 -0
  74. data/lib/automateit/shell_manager/symlink.rb +32 -0
  75. data/lib/automateit/shell_manager/which.rb +25 -0
  76. data/lib/automateit/tag_manager.rb +63 -0
  77. data/lib/automateit/tag_manager/struct.rb +101 -0
  78. data/lib/automateit/tag_manager/tag_parser.rb +91 -0
  79. data/lib/automateit/tag_manager/yaml.rb +29 -0
  80. data/lib/automateit/template_manager.rb +55 -0
  81. data/lib/automateit/template_manager/base.rb +172 -0
  82. data/lib/automateit/template_manager/erb.rb +17 -0
  83. data/lib/ext/metaclass.rb +17 -0
  84. data/lib/ext/object.rb +18 -0
  85. data/lib/hashcache.rb +22 -0
  86. data/lib/helpful_erb.rb +63 -0
  87. data/lib/nested_error.rb +33 -0
  88. data/lib/queued_logger.rb +68 -0
  89. data/lib/tempster.rb +239 -0
  90. data/misc/index_gem_repository.rb +303 -0
  91. data/misc/setup_egg.rb +12 -0
  92. data/misc/setup_gem_dependencies.sh +7 -0
  93. data/misc/setup_rubygems.sh +21 -0
  94. data/misc/which.cmd +6 -0
  95. data/spec/extras/automateit_service_sysv_test +50 -0
  96. data/spec/extras/scratch.rb +15 -0
  97. data/spec/extras/simple_recipe.rb +8 -0
  98. data/spec/integration/account_manager_spec.rb +218 -0
  99. data/spec/integration/address_manager_linux_spec.rb +119 -0
  100. data/spec/integration/address_manager_portable_spec.rb +30 -0
  101. data/spec/integration/cli_spec.rb +215 -0
  102. data/spec/integration/examples_spec.rb +54 -0
  103. data/spec/integration/examples_spec_editor.rb +71 -0
  104. data/spec/integration/package_manager_spec.rb +104 -0
  105. data/spec/integration/platform_manager_spec.rb +69 -0
  106. data/spec/integration/service_manager_sysv_spec.rb +115 -0
  107. data/spec/integration/shell_manager_spec.rb +471 -0
  108. data/spec/integration/template_manager_erb_spec.rb +31 -0
  109. data/spec/spec_helper.rb +23 -0
  110. data/spec/unit/edit_manager_spec.rb +162 -0
  111. data/spec/unit/field_manager_spec.rb +79 -0
  112. data/spec/unit/hashcache_spec.rb +28 -0
  113. data/spec/unit/interpreter_spec.rb +98 -0
  114. data/spec/unit/platform_manager_spec.rb +44 -0
  115. data/spec/unit/plugins_spec.rb +253 -0
  116. data/spec/unit/tag_manager_spec.rb +189 -0
  117. data/spec/unit/template_manager_erb_spec.rb +137 -0
  118. metadata +249 -0
  119. metadata.gz.sig +0 -0
@@ -0,0 +1,32 @@
1
+ # == ShellManager::Symlink
2
+ #
3
+ # A ShellManager driver providing access to the symbolic link +ln_s+ command
4
+ # found on Unix-like systems.
5
+ class AutomateIt::ShellManager::Symlink < AutomateIt::ShellManager::BaseLink
6
+ depends_on \
7
+ :libraries => %w(pathname),
8
+ :callbacks => [lambda{
9
+ # JRuby can make symlinks but can't read them.
10
+ RUBY_PLATFORM !~ /java|mswin/i and File.respond_to?(:symlink)
11
+ }]
12
+
13
+ def suitability(method, *args) # :nodoc:
14
+ # Level must be higher than Portable
15
+ return available? ? 2 : 0
16
+ end
17
+
18
+ # See ShellManager#provides_symlink?
19
+ def provides_symlink?
20
+ available? ? true : false
21
+ end
22
+
23
+ # See ShellManager#ln_s
24
+ def ln_s(sources, target, opts={})
25
+ _ln(sources, target, {:symbolic => true}.merge(opts))
26
+ end
27
+
28
+ # See ShellManager#ln_sf
29
+ def ln_sf(sources, target, opts={})
30
+ _ln(sources, target, {:symbolic => true, :force => true}.merge(opts))
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # == ShellManager::Which
2
+ #
3
+ # A ShellManager driver providing access to the +which+ command found on
4
+ # Unix-like systems.
5
+ class AutomateIt::ShellManager::Which < AutomateIt::ShellManager::BaseDriver
6
+ depends_on :programs => %w(which)
7
+
8
+ def suitability(method, *args) # :nodoc:
9
+ # Level must be higher than Portable
10
+ return available? ? 2 : 0
11
+ end
12
+
13
+ # See ShellManager#which
14
+ def which(command)
15
+ data = `which "#{command}" 2>&1`.chomp
16
+ return (! data.blank? && File.exists?(data)) ? data : nil
17
+ end
18
+
19
+ # See ShellManager#which!
20
+ def which!(command)
21
+ if which(command).nil?
22
+ raise ArgumentError.new("command not found: #{command}")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,63 @@
1
+ # == TagManager
2
+ #
3
+ # The TagManager provides a way of querying tags. Tags are keywords
4
+ # associated with a specific hostname or group. These are useful for grouping
5
+ # together hosts and defining common behavior for them. The tags are
6
+ # typically stored in a Project's <tt>config/tags.yml</tt> file.
7
+ #
8
+ # For example, consider a <tt>tags.yml</tt> file that contains YAML like:
9
+ # desktops:
10
+ # - satori
11
+ # - sunyata
12
+ # - michiru
13
+ # notebooks:
14
+ # - rheya
15
+ # - avijja
16
+ #
17
+ # With the above file, if we're on the host called "satori", we can query the
18
+ # fields like this:
19
+ # tags # => ["satori", "desktops", "localhost", ...]
20
+ #
21
+ # tagged?("desktops") # => true
22
+ # tagged?("notebooks") # => false
23
+ # tagged?(:satori) # => true
24
+ # tagged?("satori") # => true
25
+ # tagged?("satori || desktops") # => true
26
+ # tagged?("(satori || desktops) && !notebooks") # => true
27
+ class AutomateIt::TagManager < AutomateIt::Plugin::Manager
28
+ alias_methods :hosts_tagged_with, :tags, :tagged?, :tags_for
29
+
30
+ # Return a list of hosts that match the query. See #tagged? for information
31
+ # on query syntax.
32
+ def hosts_tagged_with(query) dispatch(query) end
33
+
34
+ # Return a list of tags for this host.
35
+ def tags() dispatch() end
36
+
37
+ # Is this host tagged with the +query+?
38
+ #
39
+ # Examples:
40
+ # tags # => ["localhost", "foo", "bar", ...]
41
+ #
42
+ # tagged?(:localhost) # => true
43
+ # tagged?("localhost") # => true
44
+ # tagged?("localhost && foo") # => true
45
+ # tagged?("localhost || foo") # => true
46
+ # tagged?("!foo") # => false
47
+ # tagged?("(localhost || foo) && bar") # => true
48
+ def tagged?(query, hostname=nil) dispatch(query, hostname) end
49
+
50
+ # Return a list of tags for the host.
51
+ def tags_for(hostname) dispatch(hostname) end
52
+ end
53
+
54
+ # == TagManager::BaseDriver
55
+ #
56
+ # Base class for all TagManager drivers.
57
+ class AutomateIt::TagManager::BaseDriver < AutomateIt::Plugin::Driver
58
+ end
59
+
60
+ # Drivers
61
+ require 'automateit/tag_manager/tag_parser'
62
+ require 'automateit/tag_manager/struct'
63
+ require 'automateit/tag_manager/yaml'
@@ -0,0 +1,101 @@
1
+ # == TagManager::Struct
2
+ #
3
+ # A TagManager driver for querying a data structure. It's not useful on its
4
+ # own, but can be subclassed by other drivers that actually load tags.
5
+ class AutomateIt::TagManager::Struct < AutomateIt::TagManager::BaseDriver
6
+ depends_on :nothing
7
+
8
+ def suitability(method, *args) # :nodoc:
9
+ return 1
10
+ end
11
+
12
+ # Options:
13
+ # * :struct -- Hash to use for queries.
14
+ def setup(opts={})
15
+ super(opts)
16
+
17
+ if opts[:struct]
18
+ @struct = AutomateIt::TagManager::TagParser.expand(opts[:struct])
19
+ @tags = Set.new
20
+ else
21
+ @struct ||= {}
22
+ @tags ||= Set.new
23
+ end
24
+ end
25
+
26
+ # Return tags, populate them if necessary.
27
+ def tags
28
+ if @tags.empty?
29
+ begin
30
+ hostnames = interpreter.address_manager.hostnames # SLOW 0.4s
31
+ @tags.merge(hostnames)
32
+ @tags.merge(tags_for(hostnames))
33
+ @tags.merge(interpreter.address_manager.addresses)
34
+ rescue NotImplementedError => e
35
+ log.debug("Can't find AddressManager for this platform: #{e}")
36
+ end
37
+
38
+ begin
39
+ @tags.merge(interpreter.platform_manager.tags)
40
+ rescue NotImplementedError => e
41
+ log.debug("Can't find PlatformManager for this platform: #{e}")
42
+ end
43
+ end
44
+ @tags
45
+ end
46
+
47
+ # See TagManager#hosts_tagged_with
48
+ def hosts_tagged_with(query)
49
+ hosts = @struct.values.flatten.uniq
50
+ return hosts.select{|hostname| tagged?(query, hostname)}
51
+ end
52
+
53
+ # See TagManager#tagged?
54
+ def tagged?(query, hostname=nil)
55
+ query = query.to_s
56
+ selected_tags = hostname ? tags_for(hostname) : tags
57
+ # XXX This tokenization process discards unknown characters, which may hide errors in the query
58
+ tokens = query.scan(%r{\!|\(|\)|\&+|\|+|!?[\.\w]+})
59
+ if tokens.size > 1
60
+ booleans = tokens.map do |token|
61
+ if matches = token.match(/^(!?)([\.\w]+)$/)
62
+ selected_tags.include?(matches[2]) && matches[1].empty?
63
+ else
64
+ token
65
+ end
66
+ end
67
+ code = booleans.join(" ")
68
+
69
+ begin
70
+ return eval(code) # XXX What could possibly go wrong?
71
+ rescue Exception => e
72
+ raise ArgumentError.new("Invalid query -- #{query}")
73
+ end
74
+ else
75
+ return selected_tags.include?(query)
76
+ end
77
+ end
78
+
79
+ # See TagManager#tags_for
80
+ def tags_for(hostnames)
81
+ hostnames = \
82
+ case hostnames
83
+ when String
84
+ interpreter.address_manager.hostnames_for(hostnames)
85
+ when Array, Set
86
+ hostnames.inject(Set.new) do |sum, hostname|
87
+ sum.merge(interpreter.address_manager.hostnames_for(hostname)); sum
88
+ end
89
+ else
90
+ raise TypeError.new("invalid hostnames argument type: #{hostnames.class}")
91
+ end
92
+ return @struct.inject(Set.new) do |sum, role_and_members|
93
+ role, members = role_and_members
94
+ members_aliases = members.inject(Set.new) do |aliases, member|
95
+ aliases.merge(interpreter.address_manager.hostnames_for(member)); aliases
96
+ end.to_a
97
+ sum.add(role) unless (hostnames & members_aliases).empty?
98
+ sum
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,91 @@
1
+ # == TagManager::TagParser
2
+ #
3
+ # Helper class for parsing tags. Not useful for users -- for internal use only.
4
+ class AutomateIt::TagManager::TagParser
5
+ attr_accessor :struct
6
+ attr_accessor :is_trace
7
+
8
+ # Create a parser for the +struct+, a hash of tag keys to values with arrays of items.
9
+ def initialize(struct, is_trace=false)
10
+ self.struct = struct
11
+ self.is_trace = is_trace
12
+ normalize!
13
+ end
14
+
15
+ # Normalize a block of text to replace shortcut symbols that cause YAML to choke.
16
+ def self.normalize(text)
17
+ return text \
18
+ .gsub(/^(\s*-\s+)(!@)/, '\1EXCLUDE_TAG ') \
19
+ .gsub(/^(\s*-\s+)(!)/, '\1EXCLUDE_HOST ') \
20
+ .gsub(/^(\s*-\s+)(@)/, '\1INCLUDE_TAG ')
21
+ end
22
+
23
+ # Normalize the contents of the internal struct.
24
+ def normalize!
25
+ for tag, items in struct
26
+ next unless items
27
+ for item in items
28
+ next unless item
29
+ item.gsub!(/^(\!@|\^@)\s*/, 'EXCLUDE_TAG ')
30
+ item.gsub!(/^(\!|\^)\s*/, 'EXCLUDE_HOST ')
31
+ item.gsub!(/^(@)\s*/, 'INCLUDE_TAG ')
32
+ end
33
+ end
34
+ end
35
+
36
+ # Display debugging information if +is_trace+ is enabled.
37
+ def trace(msg)
38
+ puts msg if is_trace
39
+ end
40
+
41
+ # Return array of hosts for the +tag+.
42
+ def hosts_for(tag)
43
+ raise IndexError.new("Unknown tag - #{tag}") unless struct[tag]
44
+ trace "\nAA %s" % tag
45
+ hosts = Set.new
46
+ for item in struct[tag]
47
+ case item
48
+ when /^INCLUDE_TAG (\w+)$/
49
+ trace "+g %s" % $1
50
+ hosts.merge(hosts_for($1))
51
+ when /^EXCLUDE_TAG (\w+)$/
52
+ trace "-g %s" % $1
53
+ hosts.subtract(hosts_for($1))
54
+ when /^EXCLUDE_HOST (\w+)$/
55
+ trace "-h %s" % $1
56
+ hosts.delete($1)
57
+ else
58
+ trace "+h %s" % item
59
+ hosts << item
60
+ end
61
+ end
62
+ result = hosts.to_a
63
+ trace "ZZ %s for %s" % [result.inspect, tag]
64
+ return result
65
+ end
66
+
67
+ # Return array of tags.
68
+ def tags
69
+ return struct.keys
70
+ end
71
+
72
+ # Expand the include/exclude/group rules and return a struct with only the
73
+ # hosts these rules produce.
74
+ def expand
75
+ result = {}
76
+ for tag in tags
77
+ result[tag] = hosts_for(tag)
78
+ end
79
+ result
80
+ end
81
+
82
+ # Replace the internal struct with an expanded version, see #expand.
83
+ def expand!
84
+ struct.replace(expand)
85
+ end
86
+
87
+ # Expand the +struct+.
88
+ def self.expand(struct, is_trace=false)
89
+ self.new(struct, is_trace).expand
90
+ end
91
+ end
@@ -0,0 +1,29 @@
1
+ # == TagManager::YAML
2
+ #
3
+ # A TagManager driver that reads tags from a YAML file.
4
+ class AutomateIt::TagManager::YAML < AutomateIt::TagManager::Struct
5
+ depends_on :nothing
6
+
7
+ def suitability(method, *args) # :nodoc:
8
+ return 5
9
+ end
10
+
11
+ # Options:
12
+ # * :file -- File to read tags from. The file is preprocessed with ERB and
13
+ # must produce YAML content.
14
+ def setup(opts={})
15
+ if filename = opts.delete(:file)
16
+ contents = _read(filename)
17
+ output = HelpfulERB.new(contents, filename).result
18
+
19
+ text = AutomateIt::TagManager::TagParser.normalize(output)
20
+ opts[:struct] = ::YAML::load(text)
21
+ end
22
+ super(opts)
23
+ end
24
+
25
+ def _read(filename)
26
+ return File.read(filename)
27
+ end
28
+ private :_read
29
+ end
@@ -0,0 +1,55 @@
1
+ # == TemplateManager
2
+ #
3
+ # TemplateManager renders templates to files.
4
+ #
5
+ # See the #render method for details.
6
+ class AutomateIt::TemplateManager < AutomateIt::Plugin::Manager
7
+ alias_methods :render
8
+
9
+ # Render a template.
10
+ #
11
+ # You may specify the +source+ and +target+ as arguments or options. For
12
+ # example, <tt>render(:file => "input", :to => "output")</tt> is the same as
13
+ # <tt>render("input", "output")</tt>.
14
+ #
15
+ # Options:
16
+ # * :file -- Read the template from this file.
17
+ # * :text -- Read the template from this string.
18
+ # * :to -- Render to a file, otherwise returns the rendered string.
19
+ # * :locals -- Hash of variables passed to template as local variables.
20
+ # * :dependencies -- When checking timestamps, include this Array of
21
+ # filenames when checking timestamps.
22
+ # * :force -- Boolean to force rendering without checking timestamps.
23
+ # * :check -- Determines when to render, otherwise uses value of
24
+ # +default_check+, possible values:
25
+ # * :compare -- Only render if rendered template is different than the
26
+ # target's contents or if the target doesn't exist.
27
+ # * :timestamp -- Only render if the target is older than the template and
28
+ # dependencies.
29
+ # * :exists -- Only render if the target doesn't exist.
30
+ #
31
+ # For example, if the file +my_template_file+ contains:
32
+ #
33
+ # Hello <%=entity%>!
34
+ #
35
+ # You could then execute:
36
+ #
37
+ # render("my_template_file", "/tmp/out", :check => :compare,
38
+ # :locals => {:entity => "world"})
39
+ #
40
+ # And this will create a <tt>/tmp/out</tt> file with:
41
+ #
42
+ # Hello world!
43
+ def render(*options) dispatch(*options) end
44
+
45
+ # Get name of default algorithm for performing checks.
46
+ def default_check() dispatch() end
47
+
48
+ # Set name of default algorithms to perform checks, e.g., :compare. See the
49
+ # #render :check option for list of check algorithms.
50
+ def default_check=(value) dispatch(value) end
51
+ end # class TemplateManager
52
+
53
+ # Drivers
54
+ require 'automateit/template_manager/base.rb'
55
+ require 'automateit/template_manager/erb.rb'
@@ -0,0 +1,172 @@
1
+ # == TemplateManager::BaseDriver
2
+ #
3
+ # Base class for all TemplateManager drivers.
4
+ class AutomateIt::TemplateManager::BaseDriver < AutomateIt::Plugin::Driver
5
+ # Name of default algorithm for performing checks, e.g., :compare
6
+ attr_accessor :default_check
7
+
8
+ # Options:
9
+ # * :default_check - Set the #default_check, e.g., :compare
10
+ def setup(opts={})
11
+ super(opts)
12
+ if opts[:default_check]
13
+ @default_check = opts[:default_check]
14
+ else
15
+ @default_check ||= :compare
16
+ end
17
+ end
18
+
19
+ # Return Array of +dependencies+ newer than +filename+. Will be empty if
20
+ # +filename+ is newer than all of the +dependencies+.
21
+ def _newer(filename, *dependencies)
22
+ updated = []
23
+ timestamp = _mtime(filename)
24
+ for dependency in dependencies
25
+ updated << dependency if _mtime(dependency) > timestamp
26
+ end
27
+ return updated
28
+ end
29
+ protected :_newer
30
+
31
+ # Does +filename+ exist?
32
+ def _exists?(filename)
33
+ return File.exists?(filename)
34
+ end
35
+ protected :_exists?
36
+
37
+ # Return the contents of +filename+.
38
+ def _read(filename)
39
+ begin
40
+ result = File.read(filename)
41
+ return result
42
+ rescue Errno::ENOENT => e
43
+ if writing?
44
+ raise e
45
+ else
46
+ return ""
47
+ end
48
+ end
49
+ end
50
+ protected :_read
51
+
52
+ # Write +contents+ to +filename+.
53
+ def _write(filename, contents)
54
+ File.open(filename, "w+"){|writer| writer.write(contents)} if writing?
55
+ return true
56
+ end
57
+ protected :_write
58
+
59
+ # Return the modification date for +filename+.
60
+ def _mtime(filename)
61
+ return _exists? ? File.mtime(filename) : nil
62
+ end
63
+ protected :_mtime
64
+
65
+ # Render a template specified in the block. It takes the same arguments and
66
+ # returns the same results as the #render call.
67
+ #
68
+ # This method is used by the #render methods for different template drivers
69
+ # and provides all the logic for parsing arguments, figuring out if a
70
+ # template should be rendered, what to do with the rendering, etc.
71
+ #
72
+ # This method calls the supplied +block+ with a hash containing:
73
+ # * :text -- Template's text.
74
+ # * :filename -- Template's filename, or nil if none. The template
75
+ # * :binder -- Binding containing the locals as variables.
76
+ # * :locals -- Hash of locals.
77
+ # * :opts -- Hash of options passed to the #_render_helper.
78
+ #
79
+ # The supplied block must return the text of the rendered template.
80
+ #
81
+ # See the TemplateManager::ERB#render method for a usage example.
82
+ def _render_helper(*options, &block) # :yields: block_opts
83
+ args, opts = args_and_opts(*options)
84
+ source_filename = args[0] || opts[:file]
85
+ target_filename = args[1] || opts[:to]
86
+ source_text = opts[:text]
87
+
88
+ raise ArgumentError.new("No source specified with :file or :text") if not source_filename and not source_text
89
+ raise Errno::ENOENT.new(source_filename) if writing? and source_filename and not _exists?(source_filename)
90
+ raise ArgumentError.new("No target specified with :to") if not target_filename
91
+
92
+ begin
93
+ # source_filename, target_filename, opts={}
94
+ opts[:check] ||= @default_check
95
+ target_exists = _exists?(target_filename)
96
+ updates = []
97
+
98
+ unless opts[:force]
99
+ case opts[:check]
100
+ when :exists
101
+ if target_exists
102
+ log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because it already exists")
103
+ return false
104
+ else
105
+ log.info(PNOTE+"Rendering '#{target_filename}' because of it doesn't exist")
106
+ end
107
+ when :timestamp
108
+ if target_exists
109
+ updates = _newer(target_filename, \
110
+ *[source_filename, opts[:dependencies]].reject{|t| t.nil?}.flatten)
111
+ if updates.empty?
112
+ log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because dependencies haven't been updated")
113
+ return false
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ target_contents = target_exists ? _read(target_filename) : ""
120
+ source_text ||= _read(source_filename)
121
+
122
+ if source_text.blank? and preview?
123
+ return true
124
+ end
125
+
126
+ binder = nil
127
+ if opts[:locals]
128
+ # Create a binding that the template can get variables from without
129
+ # polluting the Driver's namespace.
130
+ callback = lambda{
131
+ code = ""
132
+ for key in opts[:locals].keys
133
+ code << "#{key} = opts[:locals][:#{key}]\n"
134
+ end
135
+ eval code
136
+ binding
137
+ }
138
+ binder = callback.call
139
+ end
140
+
141
+ block_opts = {
142
+ :binder => binder,
143
+ :filename => source_filename,
144
+ :text => source_text,
145
+ :locals => opts[:locals],
146
+ :opts => opts,
147
+ }
148
+ output = block.call(block_opts)
149
+
150
+ case opts[:check]
151
+ when :compare
152
+ if not target_exists
153
+ log.info(PNOTE+"Rendering '#{target_filename}' because of it doesn't exist")
154
+ elsif output == target_contents
155
+ log.debug(PNOTE+"Rendering for '#{target_filename}' skipped because contents are the same")
156
+ return false
157
+ else
158
+ log.info(PNOTE+"Rendering '#{target_filename} because its contents changed")
159
+ end
160
+ when :timestamp
161
+ log.info(PNOTE+"Rendering '#{target_filename}' because of updated: #{updates.join(' ')}")
162
+ end
163
+ result = _write(target_filename, output)
164
+ return result
165
+ ensure
166
+ if opts[:mode] or opts[:user] or opts[:group]
167
+ interpreter.chperm(target_filename, :mode => opts[:mode], :user => opts[:user], :group => opts[:group])
168
+ end
169
+ end
170
+ end
171
+ protected :_render_helper
172
+ end