puppet-courseware-manager 0.5.4 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e5f648ab76b06e7ed35d6d4c39494a234373eeb
4
- data.tar.gz: ef0ce0e41aba93367e57d0b459cd38c1f54ea38d
3
+ metadata.gz: 36a9e74fc4e32ff8eba79b73b7fda4eee3e6b6d8
4
+ data.tar.gz: 6896d7dad5206c44fdef7e251e0f35b132f2a2e8
5
5
  SHA512:
6
- metadata.gz: c2355ef7dd7a4d924e16fe62f70c053d18c88304d7d13a338274f07dd4b40f2b3abadae4d81c1d6c627645696835b2e1ab71d4fa93d9e35878eb8ce236fb1062
7
- data.tar.gz: ea1525e76dd59d35fd130d54b3925c52958d80e3a7489e3962556b24846c3d88014f666f9a902e3f6e7673463ea3394cb4d1dac57736d9996da7a010276520c9
6
+ metadata.gz: f088969a94f5d90a9a9954bc301731a51f0d40e8e05493a3964cedb61ed67730bb0da0ebd7557d0ab84073d64807eadf6ce84bee4bf902c05d9a6cc1fb493c23
7
+ data.tar.gz: e75e95efd177e911c26288e386b0fbaf532dd0cfa497c5436778916fdbac88d1125d9e77c1d99f158c78ce353e75cc7d6285c46a92562d2e558847517da38e69
data/CHANGELOG.txt CHANGED
@@ -1,6 +1,14 @@
1
1
  # Courseware Manager
2
2
  ## Release Notes
3
3
 
4
+ ### v0.6.0
5
+ * Can now work with topic directories.
6
+ * Knows how to identify obsolete slides and images across all courses.
7
+ * Smarter use of `showoff info` to find which files are in use.
8
+ * Can package course variants containing only the files & images in use.
9
+ * Default to `no` when making irreversible changes to the repo.
10
+
11
+
4
12
  ### v0.5.4
5
13
  * When running non-interactively and a default is provided, don't prompt for input.
6
14
 
data/README.txt CHANGED
@@ -42,14 +42,15 @@ DESCRIPTION
42
42
  Runs validation checks on the presentation. Defaults to running all the checks.
43
43
 
44
44
  Validators:
45
- obsolete: Lists all unreferenced images and slides. This reference checks
46
- all slides and all CSS stylesheets. Case sensitive.
45
+ obsolete: Lists all unreferenced images and slides. This reference checks all
46
+ slides and all CSS stylesheets. This validation is case sensitive
47
+ and should be run from the toplevel courseware root directory.
47
48
 
48
- missing: Lists all slides that are missing. Note that this does not check
49
- for missing image files yet. Case sensitive.
49
+ missing: Lists all slides and images that are missing. This validation is case
50
+ sensitive and should be run from within an individual course directory.
50
51
 
51
52
  lint: Runs a markdown linter on each slide file, using our own style
52
- definition.
53
+ definition. This should be run within a course directory.
53
54
 
54
55
 
55
56
  * release [type]
data/bin/courseware CHANGED
@@ -86,10 +86,14 @@ config[:pdf][:license] ||= nil
86
86
  config[:release] ||= {}
87
87
  config[:release][:links] ||= []
88
88
 
89
- # Validating obsolete slides is special because we have to load all variants;
89
+ # Validating obsolete slides is special because we have to load all courses;
90
90
  # nonetheless, we do need *something* valid there, so drop in a dummy value.
91
91
  if ARGV == ['validate', 'obsolete']
92
+ config[:presfile] ||= :none
93
+
94
+ elsif ARGV.first == 'compose'
92
95
  config[:presfile] ||= 'showoff.json'
96
+
93
97
  else
94
98
  config[:presfile] ||= Courseware.choose_variant
95
99
  end
@@ -153,7 +157,7 @@ begin
153
157
  courseware.compose subject.first
154
158
 
155
159
  when :package
156
- courseware.package subject.first
160
+ courseware.package config[:presfile]
157
161
 
158
162
  when :shell
159
163
  courseware.debug
@@ -3,11 +3,12 @@ class Courseware::Composer
3
3
  @config = config
4
4
  @repository = repository || Courseware::Repository.new(config)
5
5
 
6
+ return if @config[:presfile] == :none
7
+
6
8
  if File.exists?(@config[:presfile])
7
9
  @showoff = JSON.parse(File.read(@config[:presfile]))
8
10
  @coursename = @showoff['name']
9
11
  @prefix = @showoff['name'].gsub(' ', '_')
10
- @sections = @showoff['sections']
11
12
  end
12
13
  end
13
14
 
@@ -16,31 +17,27 @@ class Courseware::Composer
16
17
 
17
18
  if subject.nil?
18
19
  display_tags
19
- raise "Please re-run this task with a list of tags to include."
20
+ raise 'Please re-run this task with a list of tags to include.'
20
21
  end
22
+ raise 'Please re-run this task with a list of tags to include.' unless subject.is_a? Array
23
+ raise 'No master section defined in `showoff.json`' unless @showoff.include? 'master'
21
24
 
22
- subject.each do |tag|
23
- key = "tag:#{tag}"
24
- @showoff['sections'].each do |section|
25
- raise 'All sections must be represented as Hashes to customize.' unless section.is_a? Hash
26
-
27
- if section.include? key
28
- raise "A section in showoff.json refers to #{section[key]}, which does not exist." unless File.exist? section[key]
29
- section['include'] = section[key]
30
- end
31
- end
32
- end
25
+ newsections = {}
26
+ @showoff['master'].each do |section|
27
+ next if (section['tags'] & subject).empty?
33
28
 
34
- # normalize the output and trim unused tags
35
- @showoff['sections'].each do |section|
36
- section.select! { |k,v| k == 'include' }
29
+ name = section['name']
30
+ newsections[name] = section['content']
37
31
  end
38
- @showoff['sections'].reject! { |section| section.empty? }
39
32
 
40
33
  name = Courseware.question('What would you like to call this variant?', 'custom').gsub(/\W+/, '_')
41
34
  desc = Courseware.question("Enter a customized description if you'd like:")
42
35
 
36
+ @showoff.delete 'tags'
37
+ @showoff.delete 'master'
38
+ @showoff['name'] = name if name
43
39
  @showoff['description'] = desc if desc
40
+ @showoff['sections'] = newsections
44
41
 
45
42
  File.write("#{name}.json", JSON.pretty_generate(@showoff))
46
43
  puts "Run your presentation with `showoff serve -f #{name}.json` or `rake present`"
@@ -51,61 +48,56 @@ class Courseware::Composer
51
48
  on_release!
52
49
  pristine!
53
50
 
54
- subject ||= Courseware.choose_variant
55
- subject = subject.to_s
56
- content = Courseware.parse_showoff(subject)
51
+ content = JSON.parse(`showoff info -jf #{subject}`)
57
52
  variant = File.basename(subject, '.json')
58
53
  current = @repository.current(@coursename)
59
54
 
60
55
  if variant == 'showoff'
61
- variant = @prefix
62
56
  output = @prefix
63
57
  else
64
58
  output = "#{@prefix}-#{variant}"
65
59
  end
66
60
 
67
- FileUtils.rm_rf "build/#{variant}"
68
- FileUtils.mkdir_p "build/#{variant}"
69
- FileUtils.cp subject, "build/#{variant}/showoff.json"
70
-
71
- content['sections'].each do |section|
72
- if section.is_a? Hash
73
- path = section['include']
74
- else
75
- path = section
76
- end
61
+ FileUtils.rm_rf "build/#{@prefix}"
62
+ FileUtils.mkdir_p "build/#{@prefix}"
63
+ FileUtils.cp subject, "build/#{@prefix}/showoff.json"
77
64
 
78
- dir = File.dirname path
79
- FileUtils.mkdir_p "build/#{variant}/#{dir}"
80
- FileUtils.cp path, "build/#{variant}/#{path}"
81
-
82
- next unless section.is_a? Hash
65
+ # Copy in only the slides used
66
+ content['files'].each do |path|
67
+ FileUtils.mkdir_p "build/#{@prefix}/#{File.dirname(path)}"
68
+ FileUtils.cp path, "build/#{@prefix}/#{path}"
69
+ end
83
70
 
84
- files = JSON.parse(File.read(path))
85
- files.each do |file|
86
- FileUtils.cp "#{dir}/#{file}", "build/#{variant}/#{dir}/#{file}"
87
- end
71
+ # Copy in only the images used
72
+ content['images'].each do |path|
73
+ FileUtils.mkdir_p "build/#{@prefix}/#{File.dirname(path)}"
74
+ FileUtils.cp path, "build/#{@prefix}/#{path}"
88
75
  end
89
76
 
90
77
  # support is special
91
- FileUtils.cp_r '../_support', "build/#{variant}/_support"
92
- FileUtils.rm_f "build/#{variant}/_support/*.pem"
93
- FileUtils.rm_f "build/#{variant}/_support/*.pub"
94
- FileUtils.rm_f "build/#{variant}/_support/aws_credentials"
78
+ FileUtils.cp_r '../_support', "build/#{@prefix}/_support"
79
+ FileUtils.rm_f "build/#{@prefix}/_support/*.pem"
80
+ FileUtils.rm_f "build/#{@prefix}/_support/*.pub"
81
+ FileUtils.rm_f "build/#{@prefix}/_support/aws_credentials"
95
82
 
96
83
  # duplicate from cwd to build/variant everything not already copied
97
- Dir.glob('*').each do |thing|
98
- next if thing == 'build'
99
- next if File.extname(thing) == '.json'
100
- next if File.exist? "build/#{variant}/#{thing}"
101
- FileUtils.ln_s "../../#{thing}", "build/#{variant}/#{thing}"
84
+ Dir.glob('**{,/*/**}/*').each do |path|
85
+ next if path.start_with? 'build'
86
+ next if path.start_with? 'stats'
87
+ next if path.start_with? 'pdf'
88
+ next if path.start_with? '_images'
89
+ next if path == 'user.js'
90
+ next if path =~ /^\w+.json$/
91
+ next if File.exist? "build/#{@prefix}/#{path}"
92
+
93
+ FileUtils.mkdir_p "build/#{@prefix}/#{File.dirname(path)}" unless File.directory?(path)
94
+ FileUtils.ln_s File.expand_path(path), "build/#{@prefix}/#{path}"
102
95
  end
103
96
 
104
- system("tar -C build --dereference -czf build/#{output}-#{current}.tar.gz #{variant}")
97
+ system("tar -C build --dereference -czf build/#{output}-#{current}.tar.gz #{@prefix}")
105
98
  if Courseware.confirm("Would you like to clean up the output build directory?")
106
- FileUtils.rm_rf "build/#{variant}"
99
+ FileUtils.rm_rf "build/#{@prefix}"
107
100
  end
108
-
109
101
  end
110
102
 
111
103
  private
@@ -7,7 +7,6 @@ class Courseware::Dummy
7
7
  showoff = Courseware.parse_showoff(@config[:presfile])
8
8
  @coursename = showoff['name']
9
9
  @prefix = showoff['name'].gsub(' ', '_')
10
- @sections = showoff['sections']
11
10
  @password = showoff['key']
12
11
  @current = showoff['courseware_release']
13
12
  end
@@ -46,16 +46,15 @@ class Courseware
46
46
  Runs validation checks on the presentation. Defaults to running all the checks.
47
47
 
48
48
  Validators:
49
- obsolete: Lists all unreferenced images and slides. This reference checks
50
- all slides and all CSS stylesheets. Case sensitive.
49
+ obsolete: Lists all unreferenced images and slides. This reference checks all
50
+ slides and all CSS stylesheets. This validation is case sensitive
51
+ and should be run from the toplevel courseware root directory.
51
52
 
52
- Note: This validator will validate all course variants.
53
-
54
- missing: Lists all slides that are missing. Note that this does not check
55
- for missing image files yet. Case sensitive.
53
+ missing: Lists all slides and images that are missing. This validation is case
54
+ sensitive and should be run from within an individual course directory.
56
55
 
57
56
  lint: Runs a markdown linter on each slide file, using our own style
58
- definition.
57
+ definition. This should be run within a course directory.
59
58
 
60
59
  * release [type]
61
60
  Orchestrate a courseware release. Defaults to `point`.
@@ -15,10 +15,11 @@ class Courseware::Manager
15
15
  @warnings = 0
16
16
  @errors = 0
17
17
 
18
+ return if @config[:presfile] == :none
19
+
18
20
  showoff = Courseware.parse_showoff(@config[:presfile])
19
21
  @coursename = showoff['name']
20
22
  @prefix = showoff['name'].gsub(' ', '_')
21
- @sections = showoff['sections']
22
23
  @password = showoff['key']
23
24
  end
24
25
 
@@ -55,13 +56,13 @@ class Courseware::Manager
55
56
  version = Courseware.increment(@repository.current(@coursename), type)
56
57
  Courseware.bailout?("Building a release for #{@coursename} version #{version}.")
57
58
 
58
- raise "Release notes not updated for #{version}" unless Courseware.grep(version, 'Release-Notes.md')
59
+ raise "Release notes not updated for #{version}" if Dir.glob('Release-Notes*').select { |path| Courseware.grep(version, path) }.empty?
59
60
 
60
61
  Courseware.dialog('Last Repository Commit', @repository.last_commit)
61
62
  Courseware.bailout?('Abort now if the commit message displayed is not what you expected.')
62
63
  build_pdfs(version)
63
64
  point_of_no_return
64
- Courseware.bailout?('Please inspect the generated PDF files and abort if corrections must be made.') do
65
+ Courseware.bailout?('Please inspect the generated PDF files and abort if corrections must be made.', true) do
65
66
  @repository.discard(@config[:stylesheet])
66
67
  end
67
68
 
@@ -1,51 +1,99 @@
1
1
  class Courseware::Manager
2
2
 
3
3
  def obsolete
4
- # We need to get all slides from all variants to determine what's obsolete.
5
- allsections = Dir.glob('*.json').collect do |variant|
6
- Courseware.parse_showoff(variant)['sections'] rescue nil
7
- end.flatten.uniq
4
+ toplevel!
8
5
 
9
- puts "Obsolete images:"
10
- Dir.glob('**/_images/*') do |file|
11
- next if File.symlink? file
12
- next if File.directory? file
13
- next if system("grep #{file} *.css */*.md >/dev/null 2>&1")
6
+ allslides = Dir.glob('_content/**/*.md')
7
+ allimages = Dir.glob('_images/**/*').reject {|path| path.include? '_images/src' }
8
+ slides = []
9
+ images = []
14
10
 
15
- puts " * #{file}"
16
- @warnings += 1
11
+ Dir.glob('*').each do |path|
12
+ next if path == 'spec'
13
+ next if path.start_with? '_'
14
+ next unless File.directory? path
15
+
16
+ print "Validating #{path}."
17
+
18
+ # stuff presentation local slides & images into the collections of available content
19
+ allslides.concat Dir.glob("#{path}/**/*.md").reject {|file| file.include?('README.md') ||
20
+ file.include?('_notes') ||
21
+ File.dirname(file) == path }
22
+
23
+ allimages.concat Dir.glob("#{path}/_images/**/*").reject {|file| file.include?('README.md') ||
24
+ file.include?('src/') ||
25
+ File.directory?(file) }
26
+
27
+ Dir.chdir(path) do
28
+ Dir.glob('*.json').each do |filename|
29
+ # determine which slides and images are actually used
30
+ content = JSON.parse(`showoff info -jf #{filename}`)
31
+ lslides = content['files'].map do |slide|
32
+ slide.start_with?('_') ? slide.sub('_shared', '_content') : "#{path}/#{slide}"
33
+ end
34
+ limages = content['images'].map do |image|
35
+ image.start_with?('_images/shared') ? image.sub('_images/shared', '_images') : "#{path}/#{image}"
36
+ end
37
+
38
+ slides.concat(lslides)
39
+ images.concat(limages)
40
+
41
+ print '.'
42
+ end
43
+ puts
44
+ end
17
45
  end
18
46
 
19
- puts "Obsolete slides:"
20
- Dir.glob('**/*.md') do |file|
21
- next if File.symlink? file
22
- next if File.directory? file
23
- next if file =~ /^_.*$|^[^\/]*$/
24
- next if allsections.include? file
47
+ # remove the intersection, and what's left over is obsolete
48
+ obs_slides = (allslides - slides.uniq!)
49
+ obs_images = (allimages - images.uniq!)
50
+
51
+ puts "Obsolete slides:" unless obs_slides.empty?
52
+ obs_slides.each do |slide|
53
+ puts " * #{slide}"
54
+ @warnings += 1
55
+ end
25
56
 
26
- puts " * #{file}"
57
+ puts "Obsolete images:" unless obs_images.empty?
58
+ obs_images.each do |image|
59
+ puts " * #{image}"
27
60
  @warnings += 1
28
61
  end
29
62
  end
30
63
 
31
64
  def missing
32
- sections = @sections.dup
65
+ courselevel!
66
+
67
+ filename = @config[:presfile]
68
+ content = JSON.parse(`showoff info -jf #{filename}`)
69
+ sections = content['files']
70
+ images = content['images']
33
71
 
34
- # This seems backwards, but we do it this way to get a case sensitive match
72
+ # This seems backwards, but we do it this way to get a case sensitive match on a case-insensitive-preserving filesystem
35
73
  # http://stackoverflow.com/questions/357754/can-i-traverse-symlinked-directories-in-ruby-with-a-glob -- Baby jesus is crying.
36
74
  Dir.glob("**{,/*/**}/*.md") do |file|
37
75
  sections.delete(file)
38
76
  end
39
- return if sections.empty?
77
+ Dir.glob("_images/**{,/*/**}/*") do |file|
78
+ images.delete(file)
79
+ end
40
80
 
41
- puts "Missing slides:"
81
+ puts "Missing slides:" unless sections.empty?
42
82
  sections.each do |slide|
43
83
  puts " * #{slide}"
44
84
  @errors += 1
45
85
  end
86
+
87
+ puts "Missing images:" unless images.empty?
88
+ images.each do |slide|
89
+ puts " * #{slide}"
90
+ @errors += 1
91
+ end
46
92
  end
47
93
 
48
94
  def lint
95
+ courselevel!
96
+
49
97
  puts "Checking Markdown style:"
50
98
  style = File.join(@config[:cachedir], 'templates', 'markdown_style.rb')
51
99
  style = File.exists?(style) ? style : 'all'
@@ -35,9 +35,15 @@ class Courseware
35
35
  answers.include? STDIN.gets.strip.downcase
36
36
  end
37
37
 
38
- def self.bailout?(message)
39
- print "#{message} Continue? [Y/n]: "
40
- unless [ 'y', 'yes', '' ].include? STDIN.gets.strip.downcase
38
+ def self.bailout?(message, required=false)
39
+ if required
40
+ print "#{message} Continue? [y/N]: "
41
+ options = ['y', 'yes']
42
+ else
43
+ print "#{message} Continue? [Y/n]: "
44
+ options = [ 'y', 'yes', '']
45
+ end
46
+ unless options.include? STDIN.gets.strip.downcase
41
47
  if block_given?
42
48
  yield
43
49
  end
@@ -83,10 +89,12 @@ class Courseware
83
89
 
84
90
  def self.choose_variant
85
91
  variants = Dir.glob('*.json')
86
- maxlen = variants.max { |x,y| x.size <=> y.size }.size - 4 # accomodate for the extension we're stripping
87
-
92
+ return :none if variants.empty?
88
93
  return 'showoff.json' if variants == ['showoff.json']
89
94
 
95
+ maxlen = variants.max { |x,y| x.size <=> y.size }.size - 4 # accomodate for the extension we're stripping
96
+
97
+
90
98
  # Ensures that the default `showoff.json` is listed first
91
99
  variants.unshift "showoff.json"
92
100
  variants.uniq!
@@ -103,31 +111,9 @@ class Courseware
103
111
  variants[idx]
104
112
  end
105
113
 
106
- # TODO: I'm not happy with this being here, but I don't see a better place for it just now
114
+ # TODO: we could use some validation here
107
115
  def self.parse_showoff(filename)
108
- showoff = JSON.parse(File.read(filename))
109
- sections = showoff['sections'].map do |entry, section|
110
- next entry if entry.is_a? String and section.nil?
111
-
112
- if entry.is_a? Hash
113
- file = entry['include']
114
- else
115
- file = section
116
- end
117
-
118
- unless file
119
- puts "Malformed entry: #{entry.inspect} - #{section.inspect}"
120
- next nil
121
- end
122
-
123
- path = File.dirname(file)
124
- data = JSON.parse(File.read(file))
125
-
126
- data.map { |source| "#{path}/#{source}" }
127
- end.flatten.compact
128
- showoff['sections'] = sections
129
-
130
- return showoff
116
+ JSON.parse(File.read(filename))
131
117
  end
132
118
 
133
119
  def self.get_component(initial)
@@ -1,4 +1,4 @@
1
1
  class Courseware
2
- VERSION = '0.5.4'
2
+ VERSION = '0.6.0'
3
3
  end
4
4
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppet-courseware-manager
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Ford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-12 00:00:00.000000000 Z
11
+ date: 2017-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mdl
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.10'
33
+ version: 0.18.2
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.10'
40
+ version: 0.18.2
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: word_wrap
43
43
  requirement: !ruby/object:Gem::Requirement