class Courseware::Manager

Attributes

coursename[R]
errors[R]
prefix[R]
warnings[R]

Public Class Methods

new(config, repository=nil, generator=nil, printer=nil) click to toggle source
# File lib/courseware/manager.rb, line 11
def initialize(config, repository=nil, generator=nil, printer=nil)
  @config     = config
  @repository = repository || Courseware::Repository.new(config)
  @generator  = generator  || Courseware::Generator.new(config)
  @warnings   = 0
  @errors     = 0

  return if @config[:presfile] == :none

  showoff     = Courseware.parse_showoff(@config[:presfile])
  @coursename = showoff['name']
  @prefix     = showoff['name'].gsub(' ', '_')
  @password   = showoff['key']
end

Public Instance Methods

lint() click to toggle source
# File lib/courseware/manager/validators.rb, line 94
def lint
  courselevel!

  puts "Checking Markdown style:"
  style  = File.join(@config[:cachedir], 'templates', 'markdown_style.rb')
  style  = File.exists?(style) ? style : 'all'
  issues = 0

  unless system('mdl', '--version')
    puts '  * Markdown linter not found: gem install mdl'
    puts
    @warnings += 1
    return
  end

  Dir.glob('**/*.md') do |file|
    next if File.symlink? file
    next if File.directory? file
    next if file =~ /^_.*$|^[^\/]*$/

    issues += 1 unless system('mdl', '-s', style, file)
  end

  if issues > 0
    puts
    puts 'Rule explanations can be found at:'
    puts '  * https://github.com/mivok/markdownlint/blob/master/docs/RULES.md'
    puts
    @warnings += issues
  end
end
missing() click to toggle source
# File lib/courseware/manager/validators.rb, line 64
def missing
  courselevel!

  filename = @config[:presfile]
  content  = JSON.parse(`showoff info -jf #{filename}`)
  sections = content['files']
  images   = content['images']

  # This seems backwards, but we do it this way to get a case sensitive match on a case-insensitive-preserving filesystem
  # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob -- Baby jesus is crying.
  Dir.glob("**{,/*/**}/*.md") do |file|
    sections.delete(file)
  end
  Dir.glob("_images/**{,/*/**}/*") do |file|
    images.delete(file)
  end

  puts "Missing slides:" unless sections.empty?
  sections.each do |slide|
    puts "  * #{slide}"
    @errors += 1
  end

  puts "Missing images:" unless images.empty?
  images.each do |slide|
    puts "  * #{slide}"
    @errors += 1
  end
end
obsolete() click to toggle source
# File lib/courseware/manager/validators.rb, line 3
def obsolete
  toplevel!

  allslides = Dir.glob('_content/**/*.md')
  allimages = Dir.glob('_images/**/*').reject {|path| path.include? '_images/src' }
  slides    = []
  images    = []

  Dir.glob('*').each do |path|
    next if path == 'spec'
    next if path.start_with? '_'
    next unless File.directory? path

    print "Validating #{path}."

    # stuff presentation local slides & images into the collections of available content
    allslides.concat Dir.glob("#{path}/**/*.md").reject {|file| file.include?('README.md')      ||
                                                                file.include?('_notes')         ||
                                                                File.dirname(file) == path }

    allimages.concat Dir.glob("#{path}/_images/**/*").reject {|file| file.include?('README.md') ||
                                                                     file.include?('src/')      ||
                                                                     File.directory?(file) }

    Dir.chdir(path) do
      Dir.glob('*.json').each do |filename|
        # determine which slides and images are actually used
        content = JSON.parse(`showoff info -jf #{filename}`)
        lslides = content['files'].map do |slide|
          slide.start_with?('_') ? slide.sub('_shared', '_content') : "#{path}/#{slide}"
        end
        limages = content['images'].map do |image|
          image.start_with?('_images/shared') ? image.sub('_images/shared', '_images') : "#{path}/#{image}"
        end

        slides.concat(lslides)
        images.concat(limages)

        print '.'
      end
      puts
    end
  end

  # remove the intersection, and what's left over is obsolete
  obs_slides = (allslides - slides.uniq!)
  obs_images = (allimages - images.uniq!)

  puts "Obsolete slides:" unless obs_slides.empty?
  obs_slides.each do |slide|
    puts "  * #{slide}"
    @warnings += 1
  end

  puts "Obsolete images:" unless obs_images.empty?
  obs_images.each do |image|
    puts "  * #{image}"
    @warnings += 1
  end
end
release(type) click to toggle source
# File lib/courseware/manager.rb, line 50
def release(type)
  courselevel!
  master!
  clean!

  @repository.update
  version = Courseware.increment(@repository.current(@coursename), type)
  Courseware.bailout?("Building a release for #{@coursename} version #{version}.")

  raise "Release notes not updated for #{version}" if Dir.glob('Release-Notes*').select { |path| Courseware.grep(version, path) }.empty?

  Courseware.dialog('Last Repository Commit', @repository.last_commit)
  Courseware.bailout?('Abort now if the commit message displayed is not what you expected.')
  build_pdfs(version)
  point_of_no_return
  Courseware.bailout?('Please inspect the generated PDF files and abort if corrections must be made.', true) do
    @repository.discard(@config[:stylesheet])
  end

  @repository.commit(@config[:stylesheet], "Updating for #{@coursename} release #{version}")
  @repository.tag("#{@prefix}-#{version}", "Releasing #{@coursename} version #{version}")

  # places the PDF files should be uploaded to
  @config[:release][:links].each do |link|
    system("open #{link}")
  end

  puts "Release shipped. Please upload PDF files to printer and break out the bubbly."
end
releasenotes() click to toggle source
# File lib/courseware/manager.rb, line 26
def releasenotes
  courselevel!
  master!
  clean!

  @repository.update
  current = @repository.current(@coursename)
  version = Courseware.increment(current)
  tag     = "#{@coursename}-#{current}"

  notes = @repository.releasenotes(tag, version)

  # print to screen
  puts notes

  # and copy if on OS X
  begin
    IO.popen('pbcopy', 'w') { |f| f.puts notes }
    puts
    puts "{{ Copied to clipboard }}"
  rescue
  end
end
wordcount(subject) click to toggle source
# File lib/courseware/manager.rb, line 80
def wordcount(subject)
  $logger.debug "Counting words for #{subject}"
  opts = print_opts(@repository.current(prefix))
  puts "Words longer than a single character:"
  Courseware::Printer.new(@config, opts) do |printer|
    subject.each do |item|
      printer.generate_html(item)
      doc   = Nokogiri::HTML(File.read('static/index.html'))
      count = doc.css('body').text.split.select {|w| w.size > 1 }.count

      puts "  * #{item}: #{count}"

      FileUtils.rm_rf('static')
    end
  end
end

Private Instance Methods

build_pdfs(version) click to toggle source
# File lib/courseware/manager.rb, line 118
def build_pdfs(version)
  @generator.styles(@coursename, version)
  Courseware::Printer.new(@config, print_opts(version)) do |printer|
    printer.print
  end
  system("open #{@config[:output]} >/dev/null 2>&1")
end
clean!() click to toggle source
# File lib/courseware/manager.rb, line 110
def clean!
  raise 'Your working directory has local modifications' unless @repository.clean?
end
courselevel!() click to toggle source
# File lib/courseware/manager.rb, line 102
def courselevel!
  raise 'This task must be run from within a course directory' unless @repository.courselevel?
end
master!() click to toggle source
# File lib/courseware/manager.rb, line 106
def master!
  raise 'You should release from the master branch' unless @repository.on_branch? 'master'
end
point_of_no_return() click to toggle source
# File lib/courseware/manager.rb, line 114
def point_of_no_return
  Courseware.dialog('Point of No Return', 'Proceeding past this point will result in permanent repository changes.')
end
print_opts(version) click to toggle source
toplevel!() click to toggle source
# File lib/courseware/manager.rb, line 98
def toplevel!
  raise 'This task must be run from the repository root.' unless @repository.toplevel?
end