ymdp 0.1.1 → 0.1.3

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/Rakefile CHANGED
@@ -20,6 +20,7 @@ begin
20
20
  gem.add_development_dependency "progressions-g", ">= 0"
21
21
  gem.add_development_dependency "bundler", ">= 0"
22
22
  gem.add_development_dependency "timer", ">= 0"
23
+ gem.add_development_dependency "serenity", ">= 0"
23
24
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
25
  end
25
26
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.3
@@ -0,0 +1,278 @@
1
+ module YMDP
2
+ module Compiler
3
+ # Command-line options processor for Compiler module.
4
+ #
5
+ class Options
6
+ # Parse command line options into an options hash.
7
+ #
8
+ def self.parse
9
+ options = {
10
+ :commit => false,
11
+ :branch => "master"
12
+ }
13
+ OptionParser.new do |opts|
14
+ options[:commit] = false
15
+ options[:verbose] = CONFIG.verbose?
16
+ opts.banner = "Usage: build.rb [options]"
17
+
18
+ opts.on("-d", "--domain [domain]", "Force Domain") do |v|
19
+ options[:domain] = v
20
+ end
21
+ opts.on("-b", "--branch [branch]", "Current Branch") do |v|
22
+ options[:branch] = v
23
+ end
24
+ opts.on("-m", "--message [message]", "Commit Message") do |v|
25
+ options[:commit] = true
26
+ options[:message] = v
27
+ end
28
+ opts.on("-n", "--no-commit", "Don't Commit") do |v|
29
+ options[:commit] = false
30
+ end
31
+ opts.on("-v", "--verbose", "Verbose (show all file writes)") do |v|
32
+ options[:verbose] = true
33
+ end
34
+ opts.on("-r", "--rake [task]", "Execute Rake task") do |v|
35
+ options[:rake] = v
36
+ end
37
+ opts.on("-c", "--compress", "Compress JavaScript and CSS") do |v|
38
+ options[:compress] = v
39
+ end
40
+ end.parse!
41
+
42
+ options
43
+ end
44
+ end
45
+
46
+ # Covers all the domains and the actions that are taken on all domains at once.
47
+ #
48
+ class Domains
49
+ attr_accessor :git, :git_hash, :message, :domains, :options
50
+
51
+ def initialize(options=nil)
52
+ @options = options || Compiler::Options.parse
53
+ @domains = @options[:domain] || all_domains
54
+ @domains = @domains.to_a
55
+ @message = @options[:message]
56
+
57
+ commit
58
+ end
59
+
60
+ # Returns all domains.
61
+ #
62
+ def all_domains
63
+ SERVERS.servers.keys
64
+ end
65
+
66
+ # Commit to git and store the hash of the commit.
67
+ #
68
+ def commit
69
+ @git = GitHelper.new
70
+ if options[:commit]
71
+ git.do_commit(@message)
72
+ end
73
+ @git_hash = git.get_hash(options[:branch])
74
+ end
75
+
76
+ # Compile the source code for all domains into their usable destination files.
77
+ #
78
+ def compile
79
+ Timer.new(:title => "YMDP").time do
80
+ clean_tmp_dir do
81
+ process_domains
82
+ end
83
+ end
84
+ rescue StandardError => e
85
+ puts e.message
86
+ puts e.backtrace
87
+ end
88
+
89
+ # Process source code for each domain in turn.
90
+ #
91
+ def process_domains
92
+ domains.each do |domain|
93
+ compiler = Compiler::Base.new(domain, git_hash, options)
94
+
95
+ compiler.clean_domain
96
+
97
+ ["views", "assets"].each do |dir|
98
+ compiler.process("#{BASE_PATH}/app/#{dir}/")
99
+ end
100
+ end
101
+
102
+ if options[:rake]
103
+ system "rake #{options[:rake]}"
104
+ end
105
+ end
106
+
107
+ # Perform a block, starting with a clean 'tmp' directory and ending with one.
108
+ #
109
+ def clean_tmp_dir
110
+ system "rm -rf #{TMP_DIR}"
111
+ system "mkdir #{TMP_DIR}"
112
+ yield
113
+ system "rm -rf #{TMP_DIR}"
114
+ system "mkdir #{TMP_DIR}"
115
+ end
116
+ end
117
+
118
+ # Compiles the source code for an individual domain.
119
+ #
120
+ # Usage:
121
+ #
122
+ # @compiler = Compiler::Base.new('staging', 'asdfh23rh2fas')
123
+ #
124
+ # You can then compile the domain:
125
+ #
126
+ # @compiler.build
127
+ #
128
+ class Base
129
+ attr_accessor :domain, :git_hash, :options
130
+
131
+ # A TemplateCompiler instance covers a single domain, handling all the processing necessary to
132
+ # convert the application source code into usable destination files ready for upload.
133
+ #
134
+ def initialize(domain, git_hash, options)
135
+ @domain = domain
136
+ @git_hash = git_hash
137
+ @options = options
138
+ end
139
+
140
+ # Do all the processing necessary to convert the application source code into usable destination files ready for upload to the server:
141
+ #
142
+ # - create server directory if necessary,
143
+ # - for each file in the source path, build the file, and
144
+ # - copy the images from the source to the destination directory.
145
+ #
146
+ def process(path)
147
+ puts "Processing #{path} for #{domain}"
148
+ create_directory("servers/#{domain}")
149
+ process_all_files(path)
150
+ process_all_translations
151
+ copy_images
152
+ end
153
+
154
+ # Process all code files (HTML and JavaScript) into usable, complete HTML files.
155
+ #
156
+ def process_all_files(path)
157
+ Dir["#{path}**/*"].each do |f|
158
+ build_file(f)
159
+ end
160
+ end
161
+
162
+ # Build this file if it's either:
163
+ # - a view, but not a partial or layout, or
164
+ # - a JavaScript file.
165
+ #
166
+ def build_file(file)
167
+ params = {
168
+ :file => file, :domain => domain, :git_hash => git_hash, :message => options[:message], :verbose => options[:verbose]
169
+ }
170
+ if build?(file)
171
+ if file =~ /(\.haml|\.erb)$/
172
+ YMDP::Template::View.new(params).build
173
+ elsif file =~ /\.js$/
174
+ YMDP::Template::JavaScript.new(params).build
175
+ end
176
+ end
177
+ end
178
+
179
+ # Convert all YRB translation files from YRB ".pres" format into a single JSON file per language.
180
+ #
181
+ def process_all_translations
182
+ puts "Processing ./app/assets/yrb/ for #{domain}"
183
+ YMDP::Base.supported_languages.each do |lang|
184
+ process_each_yrb(lang)
185
+ end
186
+ end
187
+
188
+ # Convert the YRB translation files of a single language for this domain into a single JSON file.
189
+ #
190
+ def process_each_yrb(lang)
191
+ tmp_file = "#{TMP_DIR}/keys_#{lang}.pres"
192
+
193
+ # Concatenate together all the YRB ".pres" files for this language into one file in the tmp dir.
194
+ #
195
+ Dir["#{BASE_PATH}/app/assets/yrb/#{lang}/*"].each do |path|
196
+ system "cat #{path} >> #{tmp_file}"
197
+ end
198
+
199
+ yrb = YMDP::Template::YRB.new(:file => tmp_file, :domain => domain)
200
+ yrb.build
201
+ yrb.validate if CONFIG.validate_json_assets?
202
+ system "rm #{tmp_file}"
203
+ end
204
+
205
+ # Creates a fresh destination directory structure for the code to be compiled into.
206
+ #
207
+ def clean_domain
208
+ dir = "#{YMDP_ROOT}/servers/#{domain}"
209
+ system "rm -rf #{dir}/views"
210
+ system "rm -rf #{dir}/assets/javascripts"
211
+ system "rm -rf #{dir}/assets/stylesheets"
212
+ system "rm -rf #{dir}/assets/yrb"
213
+ system "rm -rf #{TMP_DIR}/"
214
+ system "mkdir #{TMP_DIR}"
215
+ end
216
+
217
+ # Format text in a standard way for output to the screen.
218
+ #
219
+ def log(text)
220
+ "#{Time.now.to_s} #{text}"
221
+ end
222
+
223
+ # If this directory doesn't exist, create it and print that it's being created.
224
+ #
225
+ def create_directory(path)
226
+ dest = destination(path)
227
+
228
+ unless File.exists?("#{BASE_PATH}/#{path}")
229
+ puts " create #{path}"
230
+ FileUtils.mkdir_p "#{BASE_PATH}/#{path}"
231
+ end
232
+ end
233
+
234
+ # Convert a file's path from its source to its destination.
235
+ #
236
+ # The source directory is in the 'app' directory.
237
+ #
238
+ # The destination directory is made from the 'servers' root and the domain name.
239
+ #
240
+ # For example:
241
+ # - ./servers/staging
242
+ # - ./servers/alpha
243
+ #
244
+ def destination(path)
245
+ destination = path.dup
246
+ destination.gsub!("#{YMDP_ROOT}/app", "#{YMDP_ROOT}/servers/#{domain}")
247
+ end
248
+
249
+ # Images don't require any processing, just copy them over into this domain's assets directory.
250
+ #
251
+ def copy_images
252
+ if options[:verbose]
253
+ puts log("Moving images into #{YMDP_ROOT}/servers/#{domain}/assets/images...")
254
+ end
255
+ system "rm -rf #{YMDP_ROOT}/servers/#{domain}/assets/images"
256
+ system "cp -r #{YMDP_ROOT}/app/assets/images #{YMDP_ROOT}/servers/#{domain}/assets"
257
+ end
258
+
259
+ # A filename beginning with an underscore is a partial.
260
+ #
261
+ def partial?(file)
262
+ file.split("/").last =~ /^_/
263
+ end
264
+
265
+ # A file in the layouts directory is a layout.
266
+ #
267
+ def layout?(file)
268
+ file =~ /\/app\/views\/layouts\//
269
+ end
270
+
271
+ # Build if it's not a partial and not a layout.
272
+ #
273
+ def build?(file)
274
+ !partial?(file) && !layout?(file)
275
+ end
276
+ end
277
+ end
278
+ end
@@ -0,0 +1,314 @@
1
+ module YMDP
2
+ module Template
3
+ # Compiles a single file in a single domain, processing its Haml or ERB and turning
4
+ # it into usable destination files in the 'servers' directory.
5
+ #
6
+ class Base
7
+ # Usage:
8
+ #
9
+ # @template = YMDP::Template::Base.new(params)
10
+ #
11
+ # Arguments:
12
+ #
13
+ # - verbose: boolean value, output verbose notices,
14
+ # - domain: string, indicates which domain the template is compiling to,
15
+ # - file: filename of the template in questions,
16
+ # - hash: git hash of the latest commit,
17
+ # - message: commit message of the latest commit.
18
+ #
19
+ def initialize(params)
20
+ @verbose = params[:verbose]
21
+ @domain = params[:domain]
22
+ @server = SERVERS[@domain]["server"]
23
+ @file = params[:file]
24
+ @assets_directory = "/om/assets/#{SERVERS[@domain]['assets_id']}"
25
+ @hash = params[:git_hash]
26
+ @message = params[:message]
27
+
28
+ set_content_variables
29
+
30
+ @view = base_filename(@file.split("/").last)
31
+ Application.current_view = @view
32
+ end
33
+
34
+ # Parses the file 'content.yml' and adds each of its keys to the environment as
35
+ # an instance variable, so they will be available inside the template.
36
+ #
37
+ def set_content_variables
38
+ content = YAML.load_file("#{CONFIG_PATH}/content.yml")
39
+ content.each do |key, value|
40
+ attribute = "@#{key}"
41
+ instance_variable_set(attribute, value) unless instance_variable_defined?(attribute)
42
+ end
43
+ end
44
+
45
+ # If the filename begins with a _ it's a partial.
46
+ #
47
+ def partial?
48
+ @file =~ /#{BASE_PATH}\/app\/views\/_/
49
+ end
50
+
51
+ # Compile this view unless it is a partial.
52
+ #
53
+ def build
54
+ unless partial?
55
+ write_template(processed_template)
56
+ end
57
+ end
58
+
59
+ # Returns the compiled template code after its Haml or ERB has been processed.
60
+ #
61
+ def processed_template
62
+ result = ""
63
+ File.open(@file) do |f|
64
+ template = f.read
65
+ if @file =~ /\.haml$/
66
+ result = process_haml(template, @file)
67
+ else
68
+ result = process_template(template)
69
+ end
70
+ end
71
+ result
72
+ end
73
+
74
+ # Implemented in child classes, this defines what must be done to process a template.
75
+ #
76
+ def process_template(template)
77
+ raise "Define in child"
78
+ end
79
+
80
+ # Produces the destination path of this template, in the servers directory for
81
+ # the given domain.
82
+ #
83
+ # For example:
84
+ #
85
+ # source: app/views/authorize.html.haml
86
+ # destination: servers/staging/views/authorize.html.haml
87
+ #
88
+ def destination_path
89
+ # just the file, with no directory
90
+ filename = File.basename(@file)
91
+
92
+ # just the filename, with no extension
93
+ filename = convert_filename(filename)
94
+
95
+ # just the directory, with no file
96
+ directory = File.dirname(@file)
97
+
98
+ # replace the app directory with the server directory
99
+ relative_directory = directory.gsub!("#{BASE_PATH}/app", server_path)
100
+
101
+ # make the directory if it doesn't exist
102
+ FileUtils.mkdir_p(relative_directory)
103
+
104
+ "#{relative_directory}/#{filename}"
105
+ end
106
+
107
+ # Path to the servers directory for the current domain:
108
+ #
109
+ # - "./servers/staging"
110
+ # - "./servers/alpha"
111
+ #
112
+ def server_path
113
+ "#{SERVERS_PATH}/#{@domain}"
114
+ end
115
+
116
+ # Outputs a message if @verbose is on.
117
+ #
118
+ def verbose(message)
119
+ $stdout.puts(message) if @verbose
120
+ end
121
+
122
+ # Writes the input string to the destination file without adding any layout.
123
+ #
124
+ def write_template_without_layout(result)
125
+ path = destination_path
126
+
127
+ File.open(path, "w") do |f|
128
+ f.write(result)
129
+ end
130
+ verbose "Finished writing #{path}.\n"
131
+ end
132
+
133
+ def write_template_with_layout(result)
134
+ @content = result
135
+ application_layout = "#{BASE_PATH}\/app\/views\/layouts\/application.html"
136
+ haml_layout = application_layout + ".haml"
137
+ erb_layout = application_layout + ".erb"
138
+
139
+ if File.exists?(haml_layout)
140
+ layout = File.open(haml_layout) do |f|
141
+ template = f.read
142
+ process_haml(template, haml_layout)
143
+ end
144
+ elsif File.exists?(erb_layout)
145
+ layout = File.open(erb_layout) do |f|
146
+ template = f.read
147
+ process_template(erb_layout)
148
+ end
149
+ end
150
+
151
+ write_template_without_layout(layout)
152
+ end
153
+
154
+ # Write this processed template to its destination file.
155
+ #
156
+ # Overwrite in child class to define whether the class uses a template or not.
157
+ #
158
+ def write_template(result)
159
+ write_template_with_layout(result)
160
+ end
161
+ end
162
+
163
+ class View < Base
164
+ include ActionView::Helpers::TagHelper
165
+
166
+ begin
167
+ include ApplicationHelper
168
+ rescue NameError
169
+ end
170
+
171
+ include YMDP::Base
172
+ include YMDP::AssetTagHelper
173
+ include YMDP::FormTagHelper
174
+ include YMDP::LinkTagHelper
175
+
176
+ attr_accessor :output_buffer
177
+
178
+ # Filename without its extension:
179
+ #
180
+ # - "authorize.html.haml" becomes "authorize"
181
+ #
182
+ def base_filename(filename)
183
+ filename.gsub(/(\.html|\.erb|\.haml)/, "")
184
+ end
185
+
186
+ # Filename without its extension:
187
+ #
188
+ # - "authorize.html.haml" becomes "authorize"
189
+ #
190
+ def convert_filename(filename)
191
+ base_filename(filename)
192
+ end
193
+
194
+ # Process this template with ERB.
195
+ #
196
+ def process_template(template)
197
+ ERB.new(template, 0, "%<>").result(binding)
198
+ end
199
+
200
+ # Process this template with Haml.
201
+ #
202
+ def process_haml(template, filename=nil)
203
+ options = {}
204
+ if filename
205
+ options[:filename] = filename
206
+ end
207
+ Haml::Engine.new(template, options).render(self)
208
+ end
209
+
210
+ def write_template(result)
211
+ write_template_with_layout(result)
212
+ YMDP::Validator::HTML.validate(destination_path) if CONFIG.validate_html?
213
+ end
214
+ end
215
+
216
+ class JavaScript < View
217
+ def compress_js(filename)
218
+ if compress_js_assets?
219
+ validate_filename = "#{filename}.min"
220
+ YMDP::Compressor::JavaScript.compress(filename)
221
+ end
222
+ end
223
+
224
+ def write_template(result)
225
+ filename = @file.split("/").last
226
+ tmp_filename = "./tmp/#{filename}"
227
+ save_to_file(result, tmp_filename)
228
+ result = YMDP::Compressor::JavaScript.compress(tmp_filename) || result
229
+ write_template_without_layout(result)
230
+ end
231
+ end
232
+
233
+ class YRB < Base
234
+ def directory
235
+ directory = "#{BASE_PATH}/servers/#{@domain}/assets/yrb"
236
+ FileUtils.mkdir_p(directory)
237
+ directory
238
+ end
239
+
240
+ def destination_path
241
+ filename = convert_filename(@file.split("/").last)
242
+ "#{directory}/#{filename}"
243
+ end
244
+
245
+ def to_json
246
+ processed_template
247
+ end
248
+
249
+ def to_hash
250
+ JSON.parse(to_json)
251
+ end
252
+
253
+ def to_yaml
254
+ h = {}
255
+ to_hash.each do |k,v|
256
+ k = k.downcase
257
+ h[k] = "#{v}"
258
+ end
259
+ h.to_yaml
260
+ end
261
+
262
+ def processed_template
263
+ super.to_json
264
+ end
265
+
266
+ def validate
267
+ YMDP::Validator::JSON.validate(destination_path)
268
+ end
269
+
270
+ private
271
+
272
+ def base_filename(filename)
273
+ filename.gsub(/\.pres/, "")
274
+ end
275
+
276
+ def convert_filename(filename)
277
+ "#{base_filename(filename)}.json"
278
+ end
279
+
280
+ def process_template(template)
281
+ @hash = {}
282
+ lines = template.split("\n")
283
+ lines.each do |line|
284
+ unless line =~ /^[\s]*#/
285
+ line =~ /^([^\=]+)=(.+)/
286
+ key = $1
287
+ value = $2
288
+ unless key.blank?
289
+ if @hash.has_key?(key)
290
+ puts
291
+ puts "Duplicate value in #{destination_path}"
292
+ puts " #{key}=#{@hash[key]}"
293
+ puts " #{key}=#{value}"
294
+ puts
295
+ if @hash[key] == value
296
+ puts " Values are the same but duplicate values still should not exist!"
297
+ puts
298
+ end
299
+ raise "Duplicate key error"
300
+ end
301
+ @hash[key] = value
302
+ end
303
+ end
304
+ end
305
+ @hash
306
+ end
307
+
308
+ def write_template(result)
309
+ puts destination_path if CONFIG.verbose?
310
+ write_template_without_layout(result)
311
+ end
312
+ end
313
+ end
314
+ end
@@ -0,0 +1,104 @@
1
+ require 'serenity'
2
+
3
+ module YMDP
4
+ module Configuration
5
+ class Base
6
+ attr_accessor :base
7
+
8
+ def initialize(filename, base)
9
+ if File.exists?(filename)
10
+ @config = Serenity::Configuration.new(filename)
11
+ @base = base
12
+ else
13
+ file_not_found(filename)
14
+ end
15
+ end
16
+
17
+ def [](key)
18
+ @config.get(base, key)
19
+ end
20
+
21
+ def options(*args)
22
+ @config.get(base, *args)
23
+ end
24
+
25
+ def each
26
+ options.each do |name, values|
27
+ yield name, values
28
+ end
29
+ end
30
+
31
+ def file_not_found(filename)
32
+ puts
33
+ puts "Create #{filename} with the following command:\n\n ./script/config"
34
+ puts
35
+
36
+ raise "File not found: #{filename}"
37
+ end
38
+ end
39
+
40
+ class Servers < Base
41
+ def initialize
42
+ super("#{CONFIG_PATH}/servers.yml", "servers")
43
+ end
44
+
45
+ def servers
46
+ options
47
+ end
48
+ end
49
+
50
+ class Config < Base
51
+ def initialize
52
+ super("#{CONFIG_PATH}/config.yml", "config")
53
+ end
54
+
55
+ def username
56
+ options("username")
57
+ end
58
+
59
+ def password
60
+ options("password")
61
+ end
62
+
63
+ def compress_embedded_js?
64
+ options("compress", "embedded_js")
65
+ end
66
+
67
+ def compress_js_assets?
68
+ options("compress", "js_assets")
69
+ end
70
+
71
+ def compress_css?
72
+ options("compress", "css")
73
+ end
74
+
75
+ def validate_embedded_js?
76
+ options("validate", "embedded_js", YMDP_ENV)
77
+ end
78
+
79
+ def validate_js_assets?
80
+ options("validate", "js_assets", YMDP_ENV)
81
+ end
82
+
83
+ def validate_json_assets?
84
+ options("validate", "json_assets", YMDP_ENV)
85
+ end
86
+
87
+ def validate_html?
88
+ options("validate", "html", YMDP_ENV)
89
+ end
90
+
91
+ def obfuscate?
92
+ options("compress", "obfuscate")
93
+ end
94
+
95
+ def verbose?
96
+ options("verbose")
97
+ end
98
+
99
+ def growl?
100
+ options("growl")
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,5 @@
1
+ YMDP_ROOT = BASE_PATH unless defined?(YMDP_ROOT)
2
+ TMP_DIR = TMP_PATH unless defined?(TMP_DIR)
3
+
4
+ CONFIG = YMDP::Configuration::Config.new
5
+ SERVERS = YMDP::Configuration::Servers.new