Class: Regenerate::WebPage

Inherits:
Object
  • Object
show all
Includes:
Utils
Defined in:
lib/regenerate/web-page.rb

Overview

A web page which is read from a source file and regenerated to an output file (which may be the same as the source file)

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utils

#ensureDirectoryExists, #makeBackupFile

Constructor Details

#initialize(fileName) ⇒ WebPage

Returns a new instance of WebPage.



334
335
336
337
338
339
340
341
342
343
# File 'lib/regenerate/web-page.rb', line 334

def initialize(fileName)
  @fileName = fileName
  @components = []
  @currentComponent = nil
  @componentInstanceVariables = {}
  initializePageObject(PageObject.new)  # default, can be overridden by SetPageObjectClass
  @pageObjectClassNameSpecified = nil # remember name if we have specified a page object class to override the default
  @rubyComponents = []
  readFileLines
end

Instance Attribute Details

#fileNameObject (readonly)

The absolute name of the source file



332
333
334
# File 'lib/regenerate/web-page.rb', line 332

def fileName
  @fileName
end

Instance Method Details

#addRubyComponent(rubyComponent) ⇒ Object

Add a Ruby page component to this web page (so that later on it will be executed)



376
377
378
# File 'lib/regenerate/web-page.rb', line 376

def addRubyComponent(rubyComponent)
  @rubyComponents << rubyComponent
end

#checkAndEnsureOutputFileUnchanged(outFile, oldFile) ⇒ Object

Check that a newly created output file has the same contents as another (backup) file containing the old contents If it has changed, actually reset the new file to have “.new” at the end of its name, and rename the backup file to be the output file (in effect reverting the newly written output).



491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/regenerate/web-page.rb', line 491

def checkAndEnsureOutputFileUnchanged(outFile, oldFile)
  if File.exists? oldFile
    oldFileContents = File.read(oldFile)
    newFileContents = File.read(outFile)
    if oldFileContents != newFileContents
      newFileName = outFile + ".new"
      File.rename(outFile, newFileName)
      File.rename(oldFile, outFile)
      raise UnexpectedChangeError.new("New file #{newFileName} is different from old file #{outFile}: #{diffReport(newFileContents,oldFileContents)}")
    end
  else
    raise UnexpectedChangeError.new("Can't check #{outFile} against backup #{oldFile} " + 
                                    "because backup file doesn't exist")
  end
end

#classFromString(str) ⇒ Object

Get a Ruby class from a normal Ruby class name formatted using “::” separators



404
405
406
407
408
409
# File 'lib/regenerate/web-page.rb', line 404

def classFromString(str)
  # Start with Object, and look up the module one path component at a time
  str.split('::').inject(Object) do |mod, class_name|
    mod.const_get(class_name)
  end
end

#diffReport(newString, oldString) ⇒ Object

Report the difference between two strings (that should be the same)



475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/regenerate/web-page.rb', line 475

def diffReport(newString, oldString)
  i = 0
  minLength = [newString.length, oldString.length].min
  while i<minLength and newString[i] == oldString[i] do
    i += 1
  end
  diffPos = i
  newStringEndPos = [diffPos+20,newString.length].min
  oldStringEndPos = [diffPos+20, newString.length].min
  startPos = [0, diffPos-10].max
  "Different from position #{diffPos}: \n  #{newString[startPos...newStringEndPos].inspect}\n !=\n  #{oldString[startPos...newStringEndPos].inspect}"
end

#displayObject



578
579
580
581
582
583
584
585
# File 'lib/regenerate/web-page.rb', line 578

def display
  puts "=========================================================================="
  puts "Output of #{@fileName}"
  for component in @components do
    puts "--------------------------------------"
    puts(component.output)
  end
end

#executeRubyComponentsObject

Execute the Ruby components which consist of Ruby code to be evaluated in the context of the page object



563
564
565
566
567
568
569
570
571
572
573
574
575
576
# File 'lib/regenerate/web-page.rb', line 563

def executeRubyComponents
  fileDir = File.dirname(@fileName)
  #puts "Executing ruby components in directory #{fileDir} ..."
  Dir.chdir(fileDir) do
    for rubyComponent in @rubyComponents
      rubyCode = rubyComponent.text
      #puts ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
      #puts "Executing ruby (line #{rubyComponent.lineNumber}) #{rubyCode.inspect} ..."
      @pageObject.instance_eval(rubyCode, @fileName, rubyComponent.lineNumber)
      #puts "Finished executing ruby at line #{rubyComponent.lineNumber}"
    end
  end
  #puts "Finished executing ruby components."
end

#finishAtEndOfSourceFileObject

Finish the current page component after we are at the end of the source file Anything other than static HTML should be explicitly finished, and if it isn’t finished, raise an error.



463
464
465
466
467
468
469
470
471
472
# File 'lib/regenerate/web-page.rb', line 463

def finishAtEndOfSourceFile
  if @currentComponent
    if @currentComponent.is_a? StaticHtml
      @currentComponent.finishText
      @currentComponent = nil
    else
      raise ParseException.new("Unfinished last component at end of file")
    end
  end
end

#getPageObjectInstanceVar(varName) ⇒ Object

Get the value of an instance variable of the page object



365
366
367
# File 'lib/regenerate/web-page.rb', line 365

def getPageObjectInstanceVar(varName)
  @pageObject.instance_variable_get(varName)
end

#initializePageObject(pageObject) ⇒ Object

initialise the “page object”, which is the object that “owns” the defined instance variables, and the object in whose context the Ruby components are evaluated Three special instance variable values are set - @fileName, @baseDir, @baseFileName, so that they can be accessed, if necessary, by Ruby code in the Ruby code components. (if this is called a second time, it overrides whatever was set the first time) Notes on special instance variables -

@fileName and @baseDir are the absolute paths of the source file and it's containing directory.
They would be used in Ruby code that looked for other files with names or locations relative to these two.
They would generally not be expected to appear in the output content.
@baseFileName is the name of the file without any directory path components. In some cases it might be 
used within output content.


356
357
358
359
360
361
362
# File 'lib/regenerate/web-page.rb', line 356

def initializePageObject(pageObject)
  @pageObject = pageObject
  setPageObjectInstanceVar("@fileName", @fileName)
  setPageObjectInstanceVar("@baseDir", File.dirname(@fileName))
  setPageObjectInstanceVar("@baseFileName", File.basename(@fileName))
  @initialInstanceVariables = Set.new(@pageObject.instance_variables)
end

#processCommandLine(parsedCommandLine, lineNumber) ⇒ Object

Process a line of source text that has been identified as a Regenerate start and/or end of comment line



422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/regenerate/web-page.rb', line 422

def processCommandLine(parsedCommandLine, lineNumber)
  #puts "command: #{parsedCommandLine}"
  if @currentComponent && (@currentComponent.is_a? StaticHtml) # finish any current static HTML component
    @currentComponent.finishText
    @currentComponent = nil
  end
  if @currentComponent # we are in a page component other than a static HTML component
    if parsedCommandLine.sectionStart # we have already started, so we cannot start again
      raise ParseException.new("Unexpected section start #{parsedCommandLine} inside component")
    end
    @currentComponent.processEndComment(parsedCommandLine) # so, command must be a command to end the page component
    @currentComponent = nil
  else # not in any page component, so we need to start a new one
    if !parsedCommandLine.sectionStart # if it's an end command, error, because there is nothing to end
      raise ParseException.new("Unexpected section end #{parsedCommandLine}, outside of component")
    end
    if parsedCommandLine.isInstanceVar # it's a page component that defines an instance variable value
      if parsedCommandLine.hasCommentEnd # the value will be an HTML value
        startNewComponent(HtmlVariable.new, parsedCommandLine)
      else # the value will be an HTML-commented value
        startNewComponent(CommentVariable.new, parsedCommandLine)
      end
    else # not an instance var, so it must be a special command
      if parsedCommandLine.name == "ruby" # Ruby page component containing Ruby that will be executed in the 
                                          # context of the page object
        startNewComponent(RubyCode.new(lineNumber+1), parsedCommandLine)
      elsif parsedCommandLine.name == "class" # Specify Ruby class for the page object
        startNewComponent(SetPageObjectClass.new(parsedCommandLine.value), parsedCommandLine)
      else # not a known special command
        raise ParseException.new("Unknown section type #{parsedCommandLine.name.inspect}")
      end
    end
    if @currentComponent.finished # Did the processing cause the current page component to be finished?
      @currentComponent = nil # clear the current component
    end
  end
  
end

#processTextLine(line, lineNumber) ⇒ Object

Process a text line, by adding to the current page component, or if there is none, starting a new StaticHtml component.



395
396
397
398
399
400
401
# File 'lib/regenerate/web-page.rb', line 395

def processTextLine(line, lineNumber)
  #puts "text: #{line}"
  if @currentComponent == nil
    startNewComponent(StaticHtml.new)
  end
  @currentComponent.addLine(line)
end

#readFileLinesObject

Read in and parse lines from source file



523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
# File 'lib/regenerate/web-page.rb', line 523

def readFileLines
  puts "Reading source file #{@fileName} ..."
  lineNumber = 0
  File.open(@fileName).each_line do |line|
    line.chomp!
    lineNumber += 1 # track line numbers for when Ruby code needs to be executed (i.e. to populate stack traces)
    #puts "line #{lineNumber}: #{line}"
    commentLineMatch = COMMENT_LINE_REGEX.match(line)
    if commentLineMatch # it matches the Regenerate command line regex (but might not actually be a command ...)
      parsedCommandLine = ParsedRegenerateCommentLine.new(line, commentLineMatch)
      #puts "parsedCommandLine = #{parsedCommandLine}"
      if parsedCommandLine.isRegenerateCommentLine # if it is a Regenerate command line
        parsedCommandLine.checkIsValid # check it is valid, and then, 
        processCommandLine(parsedCommandLine, lineNumber) # process the command line
      else
        processTextLine(line, lineNumber) # process a text line which is not a Regenerate command line
      end
    else
      processTextLine(line, lineNumber) # process a text line which is not a Regenerate command line
    end
  end
  # After processing all source lines, the only unfinished page component permitted is a static HTML component.
  finishAtEndOfSourceFile
  #puts "Finished reading #{@fileName}."
end

#regenerateObject

Regenerate the source file (in-place)



550
551
552
553
554
# File 'lib/regenerate/web-page.rb', line 550

def regenerate
  executeRubyComponents
  writeRegeneratedFile(@fileName)
  #display
end

#regenerateToOutputFile(outFile, checkNoChanges = false) ⇒ Object

Regenerate from the source file into the output file



557
558
559
560
# File 'lib/regenerate/web-page.rb', line 557

def regenerateToOutputFile(outFile, checkNoChanges = false)
  executeRubyComponents
  writeRegeneratedFile(outFile, checkNoChanges)
end

#setPageObject(className) ⇒ Object

Set the page object to be an object with the specified class name (this can only be done once)



412
413
414
415
416
417
418
419
# File 'lib/regenerate/web-page.rb', line 412

def setPageObject(className)
  if @pageObjectClassNameSpecified
    raise ParseException("Page object class name specified more than once")
  end
  @pageObjectClassNameSpecified = className
  pageObjectClass = classFromString(className)
  initializePageObject(pageObjectClass.new)
end

#setPageObjectInstanceVar(varName, value) ⇒ Object

Set the value of an instance variable of the page object



370
371
372
373
# File 'lib/regenerate/web-page.rb', line 370

def setPageObjectInstanceVar(varName, value)
  #puts " setPageObjectInstanceVar, #{varName} = #{value.inspect}"
  @pageObject.instance_variable_set(varName, value)
end

#startNewComponent(component, startComment = nil) ⇒ Object

Add a newly started page component to this page Also process the start comment, unless it was a static HTML component, in which case there is not start comment.



383
384
385
386
387
388
389
390
391
# File 'lib/regenerate/web-page.rb', line 383

def startNewComponent(component, startComment = nil)
  component.parentPage = self
  @currentComponent = component
  #puts "startNewComponent, @currentComponent = #{@currentComponent.inspect}"
  @components << component
  if startComment
    component.processStartComment(startComment)
  end
end

#writeRegeneratedFile(outFile, checkNoChanges) ⇒ Object

Write the output of the page components to the output file (optionally checking that there are no differences between the new output and the existing output.



509
510
511
512
513
514
515
516
517
518
519
520
# File 'lib/regenerate/web-page.rb', line 509

def writeRegeneratedFile(outFile, checkNoChanges)
  backupFileName = makeBackupFile(outFile)
  File.open(outFile, "w") do |f|
    for component in @components do
      f.write(component.output)
    end
  end
  puts " wrote regenerated page to #{outFile}"
  if checkNoChanges
    checkAndEnsureOutputFileUnchanged(outFile, backupFileName)
  end
end