Class: Courseware
- Inherits:
-
Object
- Object
- Courseware
- Defined in:
- lib/courseware.rb,
lib/courseware/help.rb,
lib/courseware/utils.rb,
lib/courseware/version.rb
Defined Under Namespace
Classes: Cache, Composer, Dummy, Generator, Manager, Printer, Repository
Constant Summary collapse
- VERSION =
'0.6.2'
Class Method Summary collapse
- .bailout?(message, required = false) ⇒ Boolean
- .choose(message, options, default = nil) ⇒ Object
- .choose_variant ⇒ Object
- .confirm(message, default = true) ⇒ Object
- .dialog(header, body = nil, width = 80) ⇒ Object
- .get_component(initial) ⇒ Object
- .grep(match, filename) ⇒ Object
- .help ⇒ Object
- .increment(version, type = :point) ⇒ Object
-
.parse_showoff(filename) ⇒ Object
TODO: we could use some validation here.
- .question(message, default = nil, required = false) ⇒ Object
Instance Method Summary collapse
- #compose(subject) ⇒ Object
- #debug ⇒ Object
- #generate(subject) ⇒ Object
-
#initialize(config, configfile) ⇒ Courseware
constructor
A new instance of Courseware.
- #options(opts) ⇒ Object
- #package(subject) ⇒ Object
- #print(subject) ⇒ Object
- #release(subject) ⇒ Object
- #validate(subject) ⇒ Object
- #wordcount(subject) ⇒ Object
Constructor Details
#initialize(config, configfile) ⇒ Courseware
Returns a new instance of Courseware.
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# 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 |
Class Method Details
.bailout?(message, required = false) ⇒ Boolean
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/courseware/utils.rb', line 38 def self.bailout?(, required=false) if required print "#{} Continue? [y/N]: " = ['y', 'yes'] else print "#{} Continue? [Y/n]: " = [ 'y', 'yes', ''] end unless .include? STDIN.gets.strip.downcase if block_given? yield end raise "User cancelled" end end |
.choose(message, options, default = nil) ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/courseware/utils.rb', line 68 def self.choose(, , default = nil) body = "" .each_with_index { |item, index| body << "\n[#{index}] #{item}" } dialog(, 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....size).include? ans end ans end |
.choose_variant ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# 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! = 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:", , 0) variants[idx] end |
.confirm(message, default = true) ⇒ Object
25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/courseware/utils.rb', line 25 def self.confirm(, default=true) return default unless STDIN.tty? if default print "#{} [Y/n]: " answers = [ 'y', 'yes', '' ] else print "#{} [N/y]: " answers = [ 'y', 'yes' ] end answers.include? STDIN.gets.strip.downcase end |
.dialog(header, body = nil, width = 80) ⇒ Object
54 55 56 57 58 59 60 61 62 63 64 65 66 |
# 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) ⇒ Object
119 120 121 122 123 124 125 |
# 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) ⇒ Object
127 128 129 |
# File 'lib/courseware/utils.rb', line 127 def self.grep(match, filename) File.read(filename) =~ Regexp.new(match) end |
.help ⇒ Object
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# 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) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# 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 |
.parse_showoff(filename) ⇒ Object
TODO: we could use some validation here
115 116 117 |
# File 'lib/courseware/utils.rb', line 115 def self.parse_showoff(filename) JSON.parse(File.read(filename)) end |
.question(message, default = nil, required = false) ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
# File 'lib/courseware/utils.rb', line 5 def self.question(, default=nil, required=false) unless STDIN.tty? raise "The question '#{}' requires an answer and cannot be run non-interactively." if required return default end loop do if default print "#{} [#{default}] " else print "#{} " end answer = STDIN.gets.strip next if required and answer.empty? return answer.empty? ? default : answer end end |
Instance Method Details
#compose(subject) ⇒ Object
151 152 153 |
# File 'lib/courseware.rb', line 151 def compose(subject) @composer.build(subject) end |
#debug ⇒ Object
159 160 161 162 |
# File 'lib/courseware.rb', line 159 def debug require 'pry' binding.pry end |
#generate(subject) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# 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. 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) ⇒ Object
28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/courseware.rb', line 28 def (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) ⇒ Object
155 156 157 |
# File 'lib/courseware.rb', line 155 def package(subject) @composer.package(subject) end |
#print(subject) ⇒ Object
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/courseware.rb', line 40 def print(subject) $logger.debug "Printing #{subject}" #TODO: This should not be duplicated! opts = { :course => @manager.coursename, :prefix => @manager.prefix, :version => @repository.current(@manager.prefix), } Courseware::Printer.new(@config, opts) do |printer| subject.each do |item| case item when :handouts printer.handouts when :exercises printer.exercises when :solutions printer.solutions when :guide printer.guide else $logger.error "The #{item} document type does not exist." end end end end |
#release(subject) ⇒ Object
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# 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) ⇒ Object
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# 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) ⇒ Object
71 72 73 |
# File 'lib/courseware.rb', line 71 def wordcount(subject) @manager.wordcount(subject) end |