class Courseware

Constants

VERSION

Public Class Methods

bailout?(message, required=false) { || ... } click to toggle source
# File lib/courseware/utils.rb, line 38
def self.bailout?(message, required=false)
  if required
    print "#{message} Continue? [y/N]: "
    options = ['y', 'yes']
  else
    print "#{message} Continue? [Y/n]: "
    options = [ 'y', 'yes', '']
  end
  unless options.include? STDIN.gets.strip.downcase
    if block_given?
      yield
    end
    raise "User cancelled"
  end
end
choose(message, options, default = nil) click to toggle source
# File lib/courseware/utils.rb, line 68
def self.choose(message, options, default = nil)
  body  = ""
  options.each_with_index { |item, index| body << "\n[#{index}] #{item}" }
  dialog(message, body)

  ans = nil
  loop do
    if default
      print "Choose an option by typing its number [#{default}]: "
      ans = STDIN.gets.strip
      ans = (ans == "") ? default : Integer(ans) rescue nil
    else
      print "Choose an option by typing its number: "
      ans = Integer(STDIN.gets.strip) rescue nil
    end

    break if (0...options.size).include? ans
  end

  ans
end
choose_variant() click to toggle source
# File lib/courseware/utils.rb, line 90
def self.choose_variant
  variants = Dir.glob('*.json')
  return :none if variants.empty?
  return 'showoff.json' if variants == ['showoff.json']

  maxlen = variants.max { |x,y| x.size <=> y.size }.size - 4 # accomodate for the extension we're stripping


  # Ensures that the default `showoff.json` is listed first
  variants.unshift "showoff.json"
  variants.uniq!

  options = variants.map do |variant|
    data = JSON.parse(File.read(variant))
    name = (variant == 'showoff.json') ? 'default' : File.basename(variant, '.json')
    desc = data['description']

    "%#{maxlen}s: %s" % [name, desc]
  end

  idx = Courseware.choose("This course has several variants available:", options, 0)
  variants[idx]
end
confirm(message, default=true) click to toggle source
# File lib/courseware/utils.rb, line 25
def self.confirm(message, default=true)
  return default unless STDIN.tty?

  if default
    print "#{message} [Y/n]: "
    answers = [ 'y', 'yes', '' ]
  else
    print "#{message} [N/y]: "
    answers = [ 'y', 'yes' ]
  end
  answers.include? STDIN.gets.strip.downcase
end
dialog(header, body=nil, width=80) click to toggle source
# File lib/courseware/utils.rb, line 54
def self.dialog(header, body=nil, width=80)
  width -= 6

  puts '################################################################################'
  puts "## #{header[0..width].center(width)} ##"
  if body
    puts '##----------------------------------------------------------------------------##'
    body.wrap(width).split("\n").each do |line|
      printf "## %-#{width}s ##\n", line
    end
  end
  puts '################################################################################'
end
get_component(initial) click to toggle source
# File lib/courseware/utils.rb, line 119
def self.get_component(initial)
  puts 'The component ID for this course can be found at:'
  puts ' * https://tickets.puppetlabs.com/browse/COURSES/?selectedTab=com.atlassian.jira.jira-projects-plugin:components-panel'
  puts
  # grab the number ending the response--either the ID from the URL or the whole string
  question('Please enter the component ID or copy & paste in its URL:', initial)[/(\d*)$/]
end
grep(match, filename) click to toggle source
# File lib/courseware/utils.rb, line 127
def self.grep(match, filename)
  File.read(filename) =~ Regexp.new(match)
end
help() click to toggle source
# File lib/courseware/help.rb, line 2
  def self.help
    IO.popen("less", "w") do |less|
      less.puts <<-EOF.gsub(/^ {6}/, '')
                             Courseware Manager

        SYNOPSIS
          courseware [-c CONFIG] [-d] <verb> [subject] [subject] ...

        DESCRIPTION
          Manage the development lifecycle of Puppet courseware. This tool is not
          required for presenting the material or for contributing minor updates.

          The following verbs are recognized:

          * print
              Render course material as PDF files. This verb accepts one or more of the
              following arguments, where the default is all.

              Arguments (optional):
                 handouts: Generate handout notes
                exercises: Generate the lab exercise manual
                solutions: Generate the solution guide
                    guide: Generate the instructor guide

          * watermark
              Render watermarked PDF files. Accepts same arguements as the `print` verb.

          * generate or update
              Build new or update certain kinds of configuration. By default, this will
              update the stylesheet.

              Arguments (optional):
                skeleton <name>: Build a new course directory named <name> and generate
                                 required metadata for a Showoff presentation.

                         config: Write current configuration to a `courseware.yaml` file.

                         styles: Generate or update the stylesheet for the current version.

                          links: Ensure that all required symlinks are in place.

                       metadata: Interactively generate or update the `showoff.json` file.

          * validate
              Runs validation checks on the presentation. Defaults to running all the checks.

              Validators:
                  obsolete: Lists all unreferenced images and slides. This reference checks all
                            slides and all CSS stylesheets. This validation is case sensitive
                            and should be run from the toplevel courseware root directory.

                   missing: Lists all slides and images that are missing. This validation is case
                            sensitive and should be run from within an individual course directory.

                      lint: Runs a markdown linter on each slide file, using our own style
                            definition. This should be run within a course directory.

          * release [type]
              Orchestrate a courseware release. Defaults to `point`.

              All instructors are expected to deliver the most current point release, except
              in extraordinary cases. We follow Semver, as closely as it can be adapted to
              classroom usage. Instructors can trust that updates with high potential to cause
              classroom disruptions will never make it into a point release.

                                       http://semver.org

              Release types:
                      major: This is a major release with "breaking" changes, such as a major
                             product update, or significant classroom workflow changes. This
                             is not necessarily tied to product releases. Instructors should
                             expect to spend significant time studying the new material thoroughly.

                      minor: This indicates a significant change in content. Instructors
                             should take extra time to review updates in minor releases.
                             The release cadence is roughly once a quarter, give or take.

                      point: Release early and release often. Changes made in the regular
                             maintenance cycle will typically fit into this category.

                      notes: Display release notes since last release and copy to clipboard.

          * wordcount [type]
              Display a simple wordcount of one or more content types.

              Arguments (optional):
                 handouts: Counts words in the student handout guide
                exercises: Counts words in the lab exercise manual
                solutions: Counts words in the solution guide

          * compose [comma,separated,list,of,tags]
              Generates a variant of the complete course, using tags defined in `showoff.json`.
              The practical effect of this action is to generate a new presentation `.json` file,
              which can be displayed directly by passing the `-f` flag to Showoff, or by choosing
              a variant in the classroom `rake present` task.

          * package [variant.json]
              Package up a standalone form of a given variant of the presentation. You can pass
              in a `variant.json` file, or choose from a menu. Tarballs will be saved into the
              `build` directory and you can optionally retain the full working directory.

          * help
              You're looking at it.
      EOF
    end
  end
increment(version, type=:point) click to toggle source
# File lib/courseware/utils.rb, line 131
def self.increment(version, type=:point)
  major, minor, point = version.split('.')
  case type
  when :major
    major.sub!(/^v/, '')  # chop off the v if needed
    "v#{major.to_i + 1}.0.0"

  when :minor
    "#{major}.#{minor.to_i + 1}.0"

  when :point
    "#{major}.#{minor}.#{point.to_i + 1}"

  end
end
new(config, configfile) click to toggle source
# File lib/courseware.rb, line 10
def initialize(config, configfile)
  @config     = config
  @configfile = configfile
  @cache      = Courseware::Cache.new(config)
  @generator  = Courseware::Generator.new(config)
  @composer   = Courseware::Composer.new(config)

  if Courseware::Repository.repository?
    @repository = Courseware::Repository.new(config)
    @manager    = Courseware::Manager.new(config, @repository)
  else
    require 'courseware/dummy'
    @repository = Courseware::Dummy.new(config)
    @manager    = Courseware::Dummy.new(config)
    $logger.debug "Running outside a valid git repository."
  end
end
parse_showoff(filename) click to toggle source

TODO: we could use some validation here

# File lib/courseware/utils.rb, line 115
def self.parse_showoff(filename)
  JSON.parse(File.read(filename))
end
question(message, default=nil, required=false) click to toggle source
# File lib/courseware/utils.rb, line 5
def self.question(message, default=nil, required=false)
  unless STDIN.tty?
    raise "The question '#{message}' requires an answer and cannot be run non-interactively." if required
    return default
  end

  loop do
    if default
      print "#{message} [#{default}] "
    else
      print "#{message} "
    end

    answer = STDIN.gets.strip
    next if required and answer.empty?

    return answer.empty? ? default : answer
  end
end

Public Instance Methods

compose(subject) click to toggle source
# File lib/courseware.rb, line 151
def compose(subject)
  @composer.build(subject)
end
debug() click to toggle source
# File lib/courseware.rb, line 159
def debug
  require 'pry'
  binding.pry
end
generate(subject) click to toggle source
# File lib/courseware.rb, line 75
def generate(subject)
  $logger.debug "Generating #{subject}"
  if subject.first == :skeleton
    subject.shift
    subject.each do |course|
      @generator.skeleton course.to_s
    end
  else
    subject.each do |item|
      case item
      when :config
        @generator.saveconfig @configfile

      when :styles
        course = @manager.coursename
        prefix = @manager.prefix
        @generator.styles(course, @repository.current(prefix))

      when :links
        @generator.links

      when :metadata
        @generator.metadata

      when :rakefile
        @generator.rakefile

      when :shared
        @generator.shared

      else
        $logger.error "I don't know how to generate #{item}!"
      end
    end
  end

end
options(opts) click to toggle source
# File lib/courseware.rb, line 28
def options(opts)
  raise ArgumentError, "One or two arguments expected, not #{opts.inspect}" unless opts.size.between?(1,2)
  if opts.include? :section
    section = opts[:section]
    setting, value = opts.reject {|key, value| key == :section }.first
    @config[section][setting] = value
  else
    setting, value = opts.first
    @config[setting] = value
  end
end
package(subject) click to toggle source
# File lib/courseware.rb, line 155
def package(subject)
  @composer.package(subject)
end
print(subject) click to toggle source
release(subject) click to toggle source
# File lib/courseware.rb, line 135
def release(subject)
  case subject

  when :major, :minor, :point
    $logger.debug "Creating a #{subject} release."
    @manager.release subject

  when :notes
    $logger.debug "Generating release notes."
    @manager.releasenotes

  else
    $logger.error "I don't know how to do that yet!"
  end
end
validate(subject) click to toggle source
# File lib/courseware.rb, line 113
def validate(subject)
  $logger.debug "Validating #{subject}"
  subject.each do |item|
    case item
    when :obsolete
      @manager.obsolete

    when :missing
      @manager.missing

    when :lint
      @manager.lint

    else
      $logger.error "I don't know how to do that yet!"
    end
  end

  $logger.warn "Found #{@manager.errors} errors and #{@manager.warnings} warnings."
  exit @manager.errors + @manager.warnings
end
wordcount(subject) click to toggle source
# File lib/courseware.rb, line 71
def wordcount(subject)
  @manager.wordcount(subject)
end