BuildMaster 0.5.0

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.
data/README ADDED
@@ -0,0 +1,25 @@
1
+ = BuildMaster - Building and releasing project and site with Ruby
2
+
3
+ Homepage:: http://buildmaster.rubyforge.org
4
+ Author:: Shane
5
+ Copyright:: (c) 2006 BuildMaster on rubyforge
6
+ License:: Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
7
+
8
+ BuildMaster is a project that targets project building, releasing and site building.
9
+
10
+ Please post your question at http://groups.google.com/group/buildmaster/ or
11
+ send email to buildmaster@googlegroups.com
12
+
13
+ == Project Building and Releasing
14
+
15
+ A ruby version of the project building and releasing script as described in
16
+ "Pragmatic Project Automation" (http://www.pragmaticprogrammer.com/sk/auto/)
17
+
18
+ == Site Building
19
+
20
+ A simple template engine that can build a website by producing the content pages based on the specified source
21
+ and decrorating the content pages with a skin.
22
+
23
+ The supported content sources are <a href="http://www.w3.org/TR/xhtml11/">XHTML</a> and <a href="http://hobix.com/textile/">Textile</a>.
24
+
25
+
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'buildmaster/shell_command'
4
+ require 'buildmaster/release_control'
5
+ require 'buildmaster/ant_client'
6
+ require 'buildmaster/cvs_client'
7
+ require 'buildmaster/svn_driver'
8
+ require 'buildmaster/site'
9
+ require 'buildmaster/xtemplate'
@@ -0,0 +1,132 @@
1
+ class Ant
2
+ include Shell
3
+ def initialize(ant_file = nil, &command_runner)
4
+ @ant_file = ant_file
5
+ @ant_home = check_directory(get_environment('ANT_HOME'))
6
+ #ISSUE: what java wants to split up classpath varies from platform to platform
7
+ #and ruby, following perl, is not too hot at hinting which box it is on.
8
+ #here I assume ":" except on win32, dos, and netware. Add extra tests here as needed.
9
+ #This is the only way I know how to check if on windows
10
+ if File.directory?('C:')
11
+ @class_path_delimiter = ';'
12
+ @path_seperator='\\'
13
+ else
14
+ @class_path_delimiter = ':'
15
+ @path_seperator='/'
16
+ end
17
+ @java_command = get_java_command()
18
+ @ant_options = get_environment_or_default('ANT_OPTS').split(' ')
19
+ @ant_arguments = get_environment_or_default('ANT_ARGS').split(' ')
20
+ @ant_options.putsh(ENV['JIKESPATH']) if ENV['JIKESPATH']
21
+ @classpath=ENV['CLASSPATH']
22
+ @command_runner = command_runner
23
+ end
24
+
25
+ def get_java_command
26
+ java_home = get_environment_or_default('JAVA_HOME')
27
+ if java_home
28
+ command = [java_home, 'bin', 'java'].join(@path_seperator)
29
+ else
30
+ command = get_environment_or_default('JAVA_CMD', 'java')
31
+ end
32
+ return command
33
+ end
34
+
35
+ def project_help
36
+ launch('-projecthelp')
37
+ end
38
+
39
+ def target(name)
40
+ launch(name)
41
+ end
42
+
43
+ def launch(arguments)
44
+ local_path = "#{@ant_home}/lib/ant-launcher.jar"
45
+ if (@classpath and @classpath.length() > 0)
46
+ local_path = "#{local_path}#{@class_path_delimiter}#{@classpath}"
47
+ end
48
+ all_arguments = Array.new()
49
+ all_arguments.push(@ant_options)
50
+ all_arguments.push('-classpath', local_path)
51
+ all_arguments.push("-Dant.home=#{@ant_home}")
52
+ all_arguments.push('org.apache.tools.ant.launch.Launcher', @ant_arguments);
53
+ all_arguments.push('-f', @ant_file) if @ant_file
54
+ all_arguments.push(arguments)
55
+ command_line = "#{@java_command} #{all_arguments.join(' ')}"
56
+ run_command(command_line)
57
+ end
58
+
59
+ private :get_java_command
60
+
61
+ end
62
+
63
+ class RubyAnt
64
+ def initialize(ant_file = nil, &command_runner)
65
+ @ant_file = ant_file
66
+ @ant_home = check_directory(get_environment('ANT_HOME'))
67
+ @java_command = get_environment_or_default('JAVA_CMD', 'java')
68
+ #ISSUE: what java wants to split up classpath varies from platform to platform
69
+ #and ruby, following perl, is not too hot at hinting which box it is on.
70
+ #here I assume ":" except on win32, dos, and netware. Add extra tests here as needed.
71
+ #This is the only way I know how to check if on windows
72
+ if File.directory?('C:')
73
+ @class_path_delimiter = ';'
74
+ else
75
+ @class_path_delimiter = ':'
76
+ end
77
+ @ant_options = get_environment_or_default('ANT_OPTS').split(' ')
78
+ @ant_arguments = get_environment_or_default('ANT_ARGS').split(' ')
79
+ @ant_options.putsh(ENV['JIKESPATH']) if ENV['JIKESPATH']
80
+ @classpath=ENV['CLASSPATH']
81
+ @command_runner = command_runner
82
+ end
83
+
84
+ def launch(arguments)
85
+ local_path = "#{@ant_home}/lib/ant-launcher.jar"
86
+ if (@classpath and @classpath.length() > 0)
87
+ local_path = "#{local_path}#{@class_path_delimiter}#{@classpath}"
88
+ end
89
+ all_arguments = Array.new()
90
+ all_arguments.push(@ant_options)
91
+ all_arguments.push('-classpath', local_path)
92
+ all_arguments.push("-Dant.home=#{@ant_home}")
93
+ all_arguments.push('org.apache.tools.ant.launch.Launcher', @ant_arguments);
94
+ all_arguments.push('-f', @ant_file) if @ant_file
95
+ all_arguments.push(arguments)
96
+ command_line = "#{@java_command} #{all_arguments.join(' ')}"
97
+ run_command(command_line)
98
+ end
99
+
100
+ private
101
+ def run(command)
102
+ if (!system(command))
103
+ raise "error running command: #{command}"
104
+ end
105
+ end
106
+
107
+ def run_command(full_command)
108
+ if (@command_runner)
109
+ @command_runner.call(full_command)
110
+ else
111
+ run(full_command)
112
+ end
113
+ end
114
+
115
+ private
116
+ def check_directory(path)
117
+ raise "#{path} is not a directory" unless File.directory? path
118
+ return path
119
+ end
120
+
121
+ def get_environment(name)
122
+ value = ENV[name]
123
+ raise "#{name} is not set" unless value
124
+ return value
125
+ end
126
+
127
+ def get_environment_or_default(name, default_value='')
128
+ value = ENV[name]
129
+ value = default_value if not value
130
+ return value
131
+ end
132
+ end
@@ -0,0 +1,11 @@
1
+ class BuildFile
2
+ def initialize(file_name)
3
+ end
4
+
5
+ def increase(property_name)
6
+ return 1
7
+ end
8
+
9
+ def save()
10
+ end
11
+ end
@@ -0,0 +1,57 @@
1
+ class CvsInfo
2
+ attr_reader :root, :repository
3
+
4
+ public
5
+ def initialize(root, repository)
6
+ @root = root
7
+ @repository = repository
8
+ end
9
+
10
+ def CvsInfo.load(folder)
11
+ return CvsInfo.new(read("#{folder}/Root").strip!, read("#{folder}/Repository").strip!)
12
+ end
13
+
14
+ private
15
+ def CvsInfo.read(file)
16
+ File.open(file) do |file|
17
+ return file.gets
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ class CvsClient
24
+ include Shell
25
+ def CvsClient.load(working_directory)
26
+ return CvsClient.new(CvsInfo.load("#{working_directory}/CVS"), working_directory)
27
+ end
28
+
29
+ def initialize(cvs_info, working_directory, &command_runner)
30
+ @cvs_info = cvs_info
31
+ @working_directory = working_directory
32
+ @command_runner = command_runner
33
+ end
34
+
35
+ def command(command)
36
+ run_command "cvs -d #{@cvs_info.root} #{command} #{@working_directory}"
37
+ end
38
+
39
+ def checkout()
40
+ run_command("cvs -d #{@cvs_info.root} co -d #{@working_directory} #{@cvs_info.repository}")
41
+ end
42
+
43
+ def update(option='')
44
+ if (option.length > 0)
45
+ option = ' ' + option
46
+ end
47
+ command("update#{option}")
48
+ end
49
+
50
+ def tag(name)
51
+ command("tag #{name}")
52
+ end
53
+
54
+ def commit(comment)
55
+ command("commit -m \"#{comment}\"")
56
+ end
57
+ end
@@ -0,0 +1,62 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ module BuildMaster
4
+
5
+ class FileProcessor
6
+ def initialize(template, content_path, evaluator)
7
+ @template = template
8
+ @content_path = content_path
9
+ @evaluator = evaluator
10
+ end
11
+
12
+ def process_textile()
13
+ textile_content = IO.read(@content_path)
14
+ match_result = TEXTILE_REGX.match(textile_content)
15
+ title = ''
16
+ if match_result != nil
17
+ title = match_result[2]
18
+ textile_content = match_result.post_match
19
+ end
20
+ html_body = RedCloth.new(textile_content).to_html
21
+ html_content = <<HTML
22
+ <!DOCTYPE html
23
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
24
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
25
+ <html xmlns="http://www.w3.org/1999/xhtml">
26
+ <head>
27
+ <title>#{title}</title>
28
+ </head>
29
+ <body>
30
+ #{html_body}
31
+ </body>
32
+ </html>
33
+ HTML
34
+ return process_html_content(html_content)
35
+ end
36
+
37
+ def process_html()
38
+ return process_html_content(File.open(@content_path))
39
+ end
40
+
41
+ private
42
+ def process_html_content(source)
43
+ document_with_skin = @template.process(source) do |message|
44
+ begin
45
+ method = @evaluator.method(message)
46
+ if (method.arity == 0)
47
+ method.call
48
+ else
49
+ method.call(@content_path)
50
+ end
51
+ rescue NameError
52
+ raise TemplateException,
53
+ "unable to process message: #{message}: #{$!}" ,
54
+ caller
55
+ end
56
+ end
57
+ return document_with_skin
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,19 @@
1
+ class Release
2
+ def initialize(vcs_driver, builder)
3
+ @vcs_driver = vcs_driver
4
+ @builder = builder
5
+ end
6
+
7
+ def release_candidate(tag)
8
+ @vcs_driver.checkout
9
+ @vcs_driver.tag(tag)
10
+ @builder.build
11
+ @vcs_driver.commit
12
+ end
13
+
14
+ def release_rebuild(tag)
15
+ @vcs_driver.checkout(tag)
16
+ @builder.invoke
17
+ end
18
+ end
19
+
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/ruby
2
+
3
+ #######################################################################
4
+ =begin
5
+ runant.rb
6
+
7
+ This script is a translation of the runant.pl written by Steve Loughran.
8
+ It runs ant with/out arguments
9
+ This script has been tested with ruby1.8.2-14/WinXP
10
+
11
+ created: 2005-03-08
12
+ author: Shane Duan
13
+ =end
14
+
15
+ # Debugging flag
16
+ $debug = true
17
+
18
+ # Assumptions:
19
+ #
20
+ # - the "java" executable/script is on the command path
21
+ # - ANT_HOME has been set
22
+ # - target platform uses ":" as classpath separator or a "C:" exists (windows)
23
+ # - target platform uses "/" as directory separator.
24
+
25
+ #######################################################################
26
+ require 'Ant'
27
+
28
+ if not Ant.new().launch(ARGV)
29
+ raise 'Ant failed'
30
+ end
31
+
@@ -0,0 +1,38 @@
1
+ module Shell
2
+ private
3
+ def run(command)
4
+ if (!system(command))
5
+ raise "error running command: #{command}"
6
+ end
7
+ end
8
+
9
+ def run_command(full_command)
10
+ if (@command_runner)
11
+ @command_runner.call(full_command)
12
+ else
13
+ run(full_command)
14
+ end
15
+ end
16
+
17
+ def check_directory(path)
18
+ raise "#{path} is not a directory" unless File.directory? path
19
+ return path
20
+ end
21
+
22
+ def get_environment(name)
23
+ value = ENV[name]
24
+ raise "#{name} is not set" unless value
25
+ return value
26
+ end
27
+
28
+ def get_environment_or_default(name, default_value='')
29
+ value = ENV[name]
30
+ value = default_value if not value
31
+ return value
32
+ end
33
+
34
+ end
35
+
36
+ class SystemCommandRunner
37
+
38
+ end
@@ -0,0 +1,147 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'fileutils'
4
+ require 'redcloth'
5
+ require 'webrick'
6
+ require 'source_file_handler'
7
+ require 'file_processor'
8
+
9
+ module BuildMaster
10
+ #todo match only beginning of the file
11
+ TEXTILE_REGX = /---(-)*\n(.*)\n(-)*---/
12
+
13
+ class SiteSpec
14
+ def self.get_instance
15
+ self.new()
16
+ end
17
+
18
+ attr_accessor :output_dir, :content_dir, :template, :template_file
19
+
20
+ def validate_inputs
21
+ validate_dir(@content_dir, :content_dir)
22
+ end
23
+
24
+ def load_template
25
+ if (@template)
26
+ return XTemplate.new(@template)
27
+ else
28
+ return XTemplate.new(File.open(@template_file))
29
+ end
30
+ end
31
+
32
+ private
33
+ def validate_dir(directory, symbol)
34
+ if not directory
35
+ raise "Directory for #{symbol.id2name} not specified"
36
+ end
37
+ if not File.exists? directory
38
+ raise "Directory for #{symbol.id2name} -- <#{directory}#> does not exist"
39
+ end
40
+ if not File.directory? directory
41
+ raise "<#{directory}> should be a directory for #{symbol.id2name}"
42
+ end
43
+ end
44
+
45
+ def validate_file(file, symbol)
46
+ if not file
47
+ raise "File for #{symbol.id2name} not specified"
48
+ end
49
+ if (not File.exists? file)
50
+ raise "File for #{symbol.id2name} -- <#{file}> does not exist"
51
+ end
52
+ if not File.file? file
53
+ raise "#<{file} should be a file for #{symbol.id2name}"
54
+ end
55
+ end
56
+ end
57
+
58
+ class Site
59
+ def initialize(spec)
60
+ @spec = spec
61
+ @spec.validate_inputs
62
+ @template = @spec.load_template
63
+ end
64
+
65
+ public
66
+ def execute(arguments)
67
+ action = 'build'
68
+ if arguments.size > 0
69
+ action = arguments[0]
70
+ end
71
+ method(action).call
72
+ end
73
+
74
+ def build
75
+ @count = 0
76
+ ensure_directory_exists(@spec.output_dir)
77
+ build_directory(@spec.output_dir, @spec.content_dir, @template)
78
+ puts "Generated file count: #{@count}"
79
+ end
80
+
81
+ def server
82
+ mime_types = WEBrick::HTTPUtils::DefaultMimeTypes.update(
83
+ {"textile" => "text/plain"}
84
+ )
85
+ server = WEBrick::HTTPServer.new(
86
+ :Port => 2000,
87
+ :Logger => WEBrick::Log.new($stdout, WEBrick::Log::INFO),
88
+ :MimeTypes => mime_types
89
+ )
90
+ server.mount('/', SourceFileHandler, @spec)
91
+ server.mount('/source', WEBrick::HTTPServlet::FileHandler, @spec.content_dir, true)
92
+ ['INT', 'TERM'].each { |signal|
93
+ trap(signal){ server.shutdown}
94
+ }
95
+ server.start
96
+ end
97
+
98
+ private
99
+ def ensure_directory_exists(dir_name)
100
+ if (not File.exist?(dir_name))
101
+ ensure_directory_exists(File.join(dir_name, '..'))
102
+ Dir.mkdir dir_name
103
+ end
104
+ end
105
+
106
+ def build_directory(out_dir, content_dir, template)
107
+ Dir.foreach(content_dir) do |item|
108
+ content_path = File.join(content_dir, item)
109
+ if (item == '.' || item == '..' || item == '.svn' || item == 'CVS')
110
+ elsif (File.directory? content_path)
111
+ build_directory(File.join(out_dir, item), content_path, template)
112
+ else
113
+ @current_file_name = content_path
114
+ process_file(content_path, out_dir, content_dir, item)
115
+ @count = @count + 1
116
+ end
117
+ end
118
+ end
119
+
120
+ def process_file(content_path, out_dir, content_dir, item)
121
+ print ">> #{content_path}"
122
+ extension = File.extname(content_path)
123
+ isIndex = @current_file_name =~ /^index/
124
+ file_processor = FileProcessor.new(@template, content_path, @spec)
125
+ if (extension.casecmp('.textile') == 0)
126
+ base_name = File.basename(item, extension)
127
+ output_file = File.join(out_dir, base_name + '.html')
128
+ write(output_file, file_processor.process_textile())
129
+ elsif (extension.casecmp('.html') == 0)
130
+ output_file = File.join(out_dir, item)
131
+ write(output_file, file_processor.process_html())
132
+ else
133
+ output_file = File.join(out_dir, item)
134
+ puts " ==> #{output_file}"
135
+ FileUtils.cp content_path, output_file
136
+ end
137
+ end
138
+
139
+ def write(target_file, xhtml)
140
+ puts " ==> #{target_file}"
141
+ File.open(target_file, "w") do |file|
142
+ xhtml.write(file, 0, false, true)
143
+ end
144
+ end
145
+
146
+ end
147
+ end