puppet-courseware-manager 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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