ymdp 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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