Class: SiteFuel::SiteFuelRuntime

Inherits:
Object
  • Object
show all
Includes:
Logging
Defined in:
lib/sitefuel/SiteFuelRuntime.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Logging

#debug, #error, #fatal, #info, #logger=, #warn

Constructor Details

#initializeSiteFuelRuntime

Returns a new instance of SiteFuelRuntime.



89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 89

def initialize
  @processors = SiteFuelRuntime.find_processors
  self.logger = SiteFuelLogger.instance
  SiteFuelLogger.instance.log_style = :clean

  @only_list_recognized_files = false

  @abort_on_errors = true
  @abort_on_warnings = false

  @scm_system = nil
end

Instance Attribute Details

#abort_on_errorsObject

whether a deployment should be aborted due to errors



83
84
85
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 83

def abort_on_errors
  @abort_on_errors
end

#abort_on_warningsObject

whether a deployment should be aborted due to warnings



86
87
88
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 86

def abort_on_warnings
  @abort_on_warnings
end

#actionObject

what action is the runtime supposed to preform



62
63
64
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 62

def action
  @action
end

#deploy_fromObject

what is the source from which we are deploying



65
66
67
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 65

def deploy_from
  @deploy_from
end

#deploy_toObject

what is the source to which we are deploying



74
75
76
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 74

def deploy_to
  @deploy_to
end

#deploymentconfigurationObject

configuration loaded from a deployment.yml file



77
78
79
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 77

def deploymentconfiguration
  @deploymentconfiguration
end

#only_list_recognized_filesObject

only lists file which have a known processor



80
81
82
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 80

def only_list_recognized_files
  @only_list_recognized_files
end

#processorsObject (readonly)

gives the array of processors available to this runtime



164
165
166
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 164

def processors
  @processors
end

#scm_systemObject

what is the scm system



71
72
73
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 71

def scm_system
  @scm_system
end

#staging_directoryObject (readonly)

what is the staging directory



68
69
70
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 68

def staging_directory
  @staging_directory
end

Class Method Details

.find_processorsObject

returns a list of processors found by looking for all children of SiteFuel::Processor::AbstractProcessor

for a processor to be automatically included it has to:

  • be loaded (see #load_processors)

  • be a child class of AbstractProcessor

  • the class name must end with Processor



152
153
154
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 152

def self.find_processors
  Processor::AbstractProcessor.find_processors
end

.load_processorsObject

finds all processors under processors/ and loads them. Any file matching *Processor.rb will be loaded



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 120

def self.load_processors
  dir = File.dirname(__FILE__).split(File::SEPARATOR)
  dir = File.join(*dir[0..-2]) + File::SEPARATOR

  # build up the search pattern by taking this file's directory and shoving
  # it onto the search pattern
  patt = File.join(dir, 'sitefuel/processors/*Processor.rb')

  # find all file matching that pattern
  files = Dir[patt]
  
  # rip off the path prefix eg. 'sitefuel/lib/b/foo.rb' becomes 'b/foo.rb'
  files = files.map do |filename|
    filename.gsub(Regexp.new("^"+Regexp.escape(dir)), '')
  end

  # get rid of anything we've already loaded
  files = files.delete_if { |file| processor_loaded?(file) }

  # load whatever files we're left with
  files.each { |f| require f }
end

.processor_loaded?(file) ⇒ Boolean

gives true if the given file (typically a processor) has already been loaded (by looking into $“). Unfortunately #require is easily tricked, so this function uses some heuristics to prevent processors from being loaded twice (by basically comparing the ”core“ part of the filename)

Returns:

  • (Boolean)


107
108
109
110
111
112
113
114
115
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 107

def self.processor_loaded?(file)
  $".map do |f|
    if File.equivalent?(file, f)
      return true
    end
  end
  
  return false
end

Instance Method Details

#actionsObject

lists the actions which are possible with this runtime.



158
159
160
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 158

def actions
  [ :deploy, :stage ]
end

#add_processor(proc) ⇒ Object

adds a processor or an array of processors to the runtime



168
169
170
171
172
173
174
175
176
177
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 168

def add_processor(proc)
  case proc
    when Array
      proc.each { |p|
        @processors << p
      }
    else
      @processors << proc
  end
end

#check_messagesObject



394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 394

def check_messages
  error_count = SiteFuelLogger.instance.error_count
  if @abort_on_errors and error_count > 0
    fatal "Aborting due to errors. Use --force to deploy anyway."
    exit(-1)
  end

  warn_count = SiteFuelLogger.instance.error_count
  if @abort_on_warnings and warn_count > 0
    fatal "Aborting due to warnings. Use --ignore-warnings to deploy anyway."
    exit(-1)
  end
end

#choose_processor(filename) ⇒ Object

gives the processor to use for a given file



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 181

def choose_processor(filename)
  matching = processors.clone.delete_if {|proc| not proc.processes_file?(filename) }

  case
    when matching.length > 1
      chosen = matching.first
      raise Processor::MultipleApplicableProcessors.new(filename, matching, chosen)
    when matching.length == 1
      return matching.first
    else
      return nil
  end
end

#choose_processor!(filename) ⇒ Object

like #choose_processor but prints a message if there are clashing processors and returns the first of the clashing processors. (effectively alerting the user, but continuing to work)



199
200
201
202
203
204
205
206
207
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 199

def choose_processor!(filename)
  begin
    choose_processor(filename)
  rescue Processor::MultipleApplicableProcessors => exception
    # log the exception
    warn exception
    exception.chosen_processor
  end
end

#classify_repository_system(pull_source) ⇒ Object

decides what repository system to use (SVN or Git) based on the pull source given (eg. ssh://… is typically git; svn+ssh://… is SVN, etc.)



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 217

def classify_repository_system(pull_source)

  # note that http:// and https:// repositories, in general could be
  # anything.
  case pull_source
    when /^git:\/\/.*$/i,
         /^ssh:\/\/.*$/i,
         /^rsync:\/\/.*$/i,
         /^.*\.git$/i
      :git

    when /^svn:\/\/.*$/i,
         /^svn\+ssh:\/\/.*$/i,
         /^file:\/\/.*$/i
      :svn

    when /^([-.a-zA-Z0-9\/])*$/i
      :filesystem

    else
      raise UnknownVersioningSystem.new(pull_source)
  end
end

#classify_repository_system!(pull_source) ⇒ Object



241
242
243
244
245
246
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 241

def classify_repository_system!(pull_source)
  classify_repository_system(pull_source)
rescue UnknownVersioningSystem => exception
  fatal(exception.to_s)
  exit(-1)
end

#deployObject

create a deployment



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 410

def deploy
  # first we have to stage the files
  stage

  check_messages

  return if @deploy_to == nil

  section_divider('Deploying')

  file_tree = FileTree.new(@deploy_to)
  
  # write out content
  @resource_processors.each_key do |filename|
    results = @resource_processors[filename]
    if results == nil
      putc '.'

      # copy the file over
      file_destination = get_full_deployed_name(filename)
      info("Copying #{filename} to #{file_destination}")
      FileUtils.copy(filename, file_destination)
    else
      putc results.processor_symbol
      results.save(file_tree)
    end
    STDOUT.flush
  end


  finish
end

#find_all_files(path) ⇒ Object

gives an array listing of all files on a given path

This is a very lightweight wrapper around Dir.



453
454
455
456
457
458
459
460
461
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 453

def find_all_files(path)
  if File.directory?(path)
    Dir[File.join(path, "**/*")]
  elsif File.file?(path)
    return path
  else
    return []
  end
end

#finishObject



443
444
445
446
447
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 443

def finish
  puts ''
  puts ''
  section_divider('Finished')
end

#get_base_resource_name(filename) ⇒ Object

given a file name will remove the staging directory from it and give just the base name for the resource



288
289
290
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 288

def get_base_resource_name(filename)
  filename.gsub(Regexp.new('^'+Regexp.escape(@staging_directory)), '')
end

#get_full_deployed_name(filename) ⇒ Object

given a source file name will remove the staging directory and give the fully qualified fully qualified name to which this resource is being deployed



296
297
298
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 296

def get_full_deployed_name(filename)
  File.join(@deploy_to, get_base_resource_name(filename))
end

#pullObject

pulls files out of a given repository or file system



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 250

def pull
  section_divider 'Pulling'

  if @scm_system == nil
    @scm_system = classify_repository_system!(@deploy_from)
    info "Using #{@scm_system} version control to access #{@deploy_from}"
  end

  # TODO this should be modularized; but in general there should be a
  # 'discoverable' class that automates creation of plugins

  case @scm_system.to_sym
    when :svn
      @staging_directory = External::SVN.export(@deploy_from)

    when :git
      @staging_directory = External::GIT.shallow_clone(@deploy_from)

    when :filesystem
      # kind of dangerous because it can override the source files; it's
      # assumed every processor will not override the original.
      @staging_directory = @deploy_from

    else
      fatal "Unknown SCM system: #{@scm_system}"
      exit(-1)
  end

  info "Pulled files for staging into #{@staging_directory}"

rescue External::ProgramExitedWithFailure => exception
  fatal "Couldn't pull files from SCM:\n#{exception}"
end

#section_divider(name) ⇒ Object



209
210
211
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 209

def section_divider(name)
  puts "== #{name} ".ljust(TerminalInfo.width, '=')
end

#stageObject

implements the stage command. Staging, by itself, will give statistics on the deployment; how many bytes were saved by minification; etc.

However, #stage when part of #deploy will go and create the requisite files in a temporary directory



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 306

def stage
  pull

  return nil if @staging_directory == nil

  section_divider('Staging')
  printer = ColumnPrinter.new([5, :span, 6])

  # find all files under deploy_from
  files = find_all_files @staging_directory

  total_original_size = 0
  total_processed_size = 0
  @resource_processors = {}
  @processor_statistics = Hash.new([0, 0, 0])
  files.each do |filename|
    processor = choose_processor!(filename)
    if processor == nil
      @resource_processors[filename] = nil
    else
      resource_name = get_base_resource_name(filename)
      @resource_processors[filename] = processor.process_file(filename, :resource_name => resource_name)
    end
    
    processor = @resource_processors[filename]
    if processor == nil
      if only_list_recognized_files == false
        printer.row('--', filename)
      end
    else
      total_original_size += processor.original_size
      total_processed_size += processor.processed_size

      stats = @processor_statistics[processor.class.processor_name].clone
      stats[0] += 1
      stats[1] += processor.original_size
      stats[2] += processor.processed_size
      @processor_statistics[processor.class.processor_name] = stats

      printer.row(
        cyan(processor.class.processor_name),
        filename,
        '%4.3f'%(processor.processed_size.prec_f/processor.original_size.prec_f)
      )
    end
  end

  printer.divider
  
  puts 'Size delta:         %+5d bytes; %4.3f' %
          [
            total_processed_size - total_original_size,
            total_processed_size.prec_f/total_original_size.prec_f
          ]

  staging_statistics
end

#staging_statisticsObject

outputs a little grid showing the number of files of each processor and the total savings

todo: this should be computed in the staging step



369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 369

def staging_statistics

  puts ''

  printer = ColumnPrinter.new([12, 8, :span, 6], [TerminalInfo.width, 50].min)
  printer.alignment([:left, :right, :right, :right])
  printer.mode(:divided)

  headers = %w{processor files delta ratio}.map{|v| bold(v)}
  printer.row(*headers)
  printer.divider
  
  @processor_statistics.keys.sort.each do |key|
    printer.row(
      key,
      @processor_statistics[key][0],
      '%+12d'%(@processor_statistics[key][2] - @processor_statistics[key][1]),
      '%4.3f'%(@processor_statistics[key][2].prec_f / @processor_statistics[key][1].prec_f)
    )
  end
  printer.divider
  puts ''
end

#verbosity(level = 1) ⇒ Object

changes the verbosity of the runtime by adjusting the log level



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
# File 'lib/sitefuel/SiteFuelRuntime.rb', line 465

def verbosity(level = 1)
  case level
    when 0
      SiteFuelLogger.instance.level = Logger::FATAL

    when 1
      SiteFuelLogger.instance.level = Logger::ERROR

    when 2
      SiteFuelLogger.instance.level = Logger::WARN

    when 3
      SiteFuelLogger.instance.level = Logger::INFO

    when 4
      SiteFuelLogger.instance.level = Logger::DEBUG

    else
      warn "Unknown verbosity level: #{level}; ignoring."
  end
end