automateit 0.70923

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.
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