leg 0.0.1 → 0.0.2

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.
Files changed (78) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +10 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +34 -0
  6. data/LICENSE +21 -0
  7. data/README.md +59 -0
  8. data/Rakefile +11 -0
  9. data/TUTORIAL.md +243 -0
  10. data/bin/console +9 -0
  11. data/bin/setup +6 -0
  12. data/exe/leg +6 -0
  13. data/leg.gemspec +31 -0
  14. data/lib/leg.rb +27 -0
  15. data/lib/leg/cli.rb +42 -0
  16. data/lib/leg/commands.rb +19 -0
  17. data/lib/leg/commands/amend.rb +49 -0
  18. data/lib/leg/commands/base_command.rb +99 -0
  19. data/lib/leg/commands/build.rb +126 -0
  20. data/lib/leg/commands/commit.rb +48 -0
  21. data/lib/leg/commands/diff.rb +29 -0
  22. data/lib/leg/commands/help.rb +43 -0
  23. data/lib/leg/commands/init.rb +46 -0
  24. data/lib/leg/commands/reset.rb +26 -0
  25. data/lib/leg/commands/resolve.rb +31 -0
  26. data/lib/leg/commands/save.rb +31 -0
  27. data/lib/leg/commands/status.rb +54 -0
  28. data/lib/leg/commands/step.rb +31 -0
  29. data/lib/leg/config.rb +42 -0
  30. data/lib/leg/default_templates.rb +340 -0
  31. data/lib/leg/diff.rb +151 -0
  32. data/lib/leg/diff_transformers.rb +11 -0
  33. data/lib/leg/diff_transformers/base_transformer.rb +13 -0
  34. data/lib/leg/diff_transformers/fold_sections.rb +89 -0
  35. data/lib/leg/diff_transformers/omit_adjacent_removals.rb +38 -0
  36. data/lib/leg/diff_transformers/syntax_highlight.rb +32 -0
  37. data/lib/leg/diff_transformers/trim_blank_lines.rb +25 -0
  38. data/lib/leg/line.rb +83 -0
  39. data/lib/leg/markdown.rb +20 -0
  40. data/lib/leg/page.rb +27 -0
  41. data/lib/leg/representations.rb +9 -0
  42. data/lib/leg/representations/base_representation.rb +42 -0
  43. data/lib/leg/representations/git.rb +388 -0
  44. data/lib/leg/representations/litdiff.rb +85 -0
  45. data/lib/leg/step.rb +16 -0
  46. data/lib/leg/template.rb +95 -0
  47. data/lib/leg/tutorial.rb +49 -0
  48. data/lib/leg/version.rb +3 -0
  49. metadata +112 -38
  50. data/bin/leg +0 -9
  51. data/lib/snaptoken.rb +0 -24
  52. data/lib/snaptoken/cli.rb +0 -61
  53. data/lib/snaptoken/commands.rb +0 -13
  54. data/lib/snaptoken/commands/amend.rb +0 -27
  55. data/lib/snaptoken/commands/base_command.rb +0 -92
  56. data/lib/snaptoken/commands/build.rb +0 -107
  57. data/lib/snaptoken/commands/commit.rb +0 -27
  58. data/lib/snaptoken/commands/help.rb +0 -38
  59. data/lib/snaptoken/commands/resolve.rb +0 -27
  60. data/lib/snaptoken/commands/status.rb +0 -21
  61. data/lib/snaptoken/commands/step.rb +0 -35
  62. data/lib/snaptoken/default_templates.rb +0 -287
  63. data/lib/snaptoken/diff.rb +0 -180
  64. data/lib/snaptoken/diff_line.rb +0 -54
  65. data/lib/snaptoken/diff_transformers.rb +0 -9
  66. data/lib/snaptoken/diff_transformers/base_transformer.rb +0 -9
  67. data/lib/snaptoken/diff_transformers/fold_sections.rb +0 -85
  68. data/lib/snaptoken/diff_transformers/omit_adjacent_removals.rb +0 -28
  69. data/lib/snaptoken/diff_transformers/trim_blank_lines.rb +0 -21
  70. data/lib/snaptoken/markdown.rb +0 -18
  71. data/lib/snaptoken/page.rb +0 -64
  72. data/lib/snaptoken/representations.rb +0 -8
  73. data/lib/snaptoken/representations/base_representation.rb +0 -38
  74. data/lib/snaptoken/representations/git.rb +0 -262
  75. data/lib/snaptoken/representations/litdiff.rb +0 -81
  76. data/lib/snaptoken/step.rb +0 -27
  77. data/lib/snaptoken/template.rb +0 -53
  78. data/lib/snaptoken/tutorial.rb +0 -64
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "leg"
5
+
6
+ include Leg
7
+
8
+ require "pry"
9
+ Pry.start
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
data/exe/leg ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'leg'
4
+
5
+ status = Leg::CLI.new.run(ARGV)
6
+ exit! if status == false
@@ -0,0 +1,31 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "leg/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "leg"
7
+ spec.version = Leg::VERSION
8
+ spec.authors = ["Jeremy Ruten"]
9
+ spec.email = ["jeremy.ruten@gmail.com"]
10
+
11
+ spec.summary = %q{Tools for creating step-by-step programming tutorials}
12
+ #spec.description = %q{TODO: Write a longer description or delete this line.}
13
+ spec.homepage = "https://github.com/yjerem/leg"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_runtime_dependency 'rugged', '0.27.2'
24
+ spec.add_runtime_dependency 'redcarpet', '3.4.0'
25
+ spec.add_runtime_dependency 'rouge', '2.0.7'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.16"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency "pry"
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'erb'
2
+ require 'fileutils'
3
+ require 'open3'
4
+ require 'optparse'
5
+ require 'redcarpet'
6
+ require 'rouge'
7
+ require 'rouge/plugins/redcarpet'
8
+ require 'rugged'
9
+ require 'yaml'
10
+
11
+ require 'leg/cli'
12
+ require 'leg/commands'
13
+ require 'leg/config'
14
+ require 'leg/default_templates'
15
+ require 'leg/diff'
16
+ require 'leg/diff_transformers'
17
+ require 'leg/line'
18
+ require 'leg/markdown'
19
+ require 'leg/page'
20
+ require 'leg/representations'
21
+ require 'leg/step'
22
+ require 'leg/template'
23
+ require 'leg/tutorial'
24
+ require 'leg/version'
25
+
26
+ module Leg
27
+ end
@@ -0,0 +1,42 @@
1
+ module Leg
2
+ class CLI
3
+ def initialize(options = {})
4
+ @options = options
5
+
6
+ initial_dir = FileUtils.pwd
7
+
8
+ @config = nil
9
+ last_dir = nil
10
+ while FileUtils.pwd != last_dir
11
+ if File.exist?("leg.yml")
12
+ @config = Leg::Config.new(FileUtils.pwd)
13
+ @config.load!
14
+ break
15
+ end
16
+
17
+ last_dir = FileUtils.pwd
18
+ FileUtils.cd("..")
19
+ end
20
+
21
+ FileUtils.cd(initial_dir)
22
+ end
23
+
24
+ def run(args)
25
+ args = ["help"] if args.empty?
26
+ cmd_name = args.shift.downcase
27
+
28
+ if cmd_name =~ /\A\d+\z/
29
+ args.unshift(cmd_name)
30
+ cmd_name = "step"
31
+ end
32
+
33
+ if cmd = Leg::Commands::LIST.find { |cmd| cmd.name == cmd_name }
34
+ command = cmd.new(args, @config)
35
+ command.opts[:quiet] = true if @options[:force_quiet]
36
+ command.run
37
+ else
38
+ puts "There is no '#{cmd_name}' command. Run `leg help` for help."
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'leg/commands/base_command'
2
+
3
+ module Leg
4
+ module Commands
5
+ LIST = []
6
+ end
7
+ end
8
+
9
+ require 'leg/commands/init'
10
+ require 'leg/commands/build'
11
+ require 'leg/commands/status'
12
+ require 'leg/commands/step'
13
+ require 'leg/commands/diff'
14
+ require 'leg/commands/commit'
15
+ require 'leg/commands/amend'
16
+ require 'leg/commands/save'
17
+ require 'leg/commands/resolve'
18
+ require 'leg/commands/reset'
19
+ require 'leg/commands/help'
@@ -0,0 +1,49 @@
1
+ module Leg
2
+ module Commands
3
+ class Amend < BaseCommand
4
+ def self.name
5
+ "amend"
6
+ end
7
+
8
+ def self.summary
9
+ "Modify a step."
10
+ end
11
+
12
+ def self.usage
13
+ "[-s]"
14
+ end
15
+
16
+ def setopts!(o)
17
+ o.on("-m", "--message MESSAGE", "Set the step summary to MESSAGE") do |m|
18
+ @opts[:message] = m
19
+ end
20
+ o.on("-d", "--default-message", "Leave the step summary unchanged, or set it to a default if empty") do |d|
21
+ @opts[:default_message] = d
22
+ end
23
+ o.on("-s", "--stay", "Don't resolve rest of steps yet") do |s|
24
+ @opts[:stay] = s
25
+ end
26
+ end
27
+
28
+ def run
29
+ needs! :config, :repo
30
+
31
+ commit_options = {
32
+ amend: true,
33
+ no_rebase: @opts[:stay],
34
+ message: @opts[:message],
35
+ use_default_message: @opts[:default_message]
36
+ }
37
+
38
+ if @git.commit!(commit_options)
39
+ unless @opts[:stay]
40
+ git_to_litdiff!
41
+ output "Success!\n"
42
+ end
43
+ else
44
+ output "Looks like you've got a conflict to resolve!\n"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,99 @@
1
+ module Leg
2
+ module Commands
3
+ class BaseCommand
4
+ attr_reader :config, :opts
5
+
6
+ def initialize(args, config)
7
+ @args = args
8
+ @config = config
9
+ @git = Leg::Representations::Git.new(@config)
10
+ @litdiff = Leg::Representations::Litdiff.new(@config)
11
+ parseopts!
12
+ end
13
+
14
+ def self.name; raise NotImplementedError; end
15
+ def self.summary; raise NotImplementedError; end
16
+ def setopts!(o); raise NotImplementedError; end
17
+ def run; raise NotImplementedError; end
18
+
19
+ def self.inherited(subclass)
20
+ Leg::Commands::LIST << subclass
21
+ end
22
+
23
+ def parseopts!
24
+ parser = OptionParser.new do |o|
25
+ o.banner = "Usage: leg #{self.class.name} #{self.class.usage}"
26
+ self.class.summary.split("\n").each do |line|
27
+ o.separator " #{line}"
28
+ end
29
+ o.separator ""
30
+ o.separator "Options:"
31
+ setopts!(o)
32
+ o.on_tail("-h", "--help", "Show this message") do
33
+ puts o
34
+ exit
35
+ end
36
+ end
37
+ @opts = {}
38
+ parser.parse!(@args)
39
+ rescue OptionParser::InvalidOption, OptionParser::InvalidArgument => e
40
+ puts "#{e.message}"
41
+ puts
42
+ parser.parse("--help")
43
+ end
44
+
45
+ def needs!(*whats)
46
+ whats.each do |what|
47
+ case what
48
+ when :config
49
+ if @config.nil?
50
+ puts "Error: You are not in a leg working directory."
51
+ exit 1
52
+ end
53
+ when :repo
54
+ if @litdiff.modified? and @git.modified?
55
+ puts "Error: doc/ and .leg/repo have diverged!"
56
+ exit 1
57
+ elsif @litdiff.modified? or !@git.exists?
58
+ litdiff_to_git!
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def output(text)
65
+ print text unless @opts[:quiet]
66
+ end
67
+
68
+ def git_to_litdiff!
69
+ tutorial = @git.load! do |step_num|
70
+ output "\r\e[K[repo/ -> Tutorial] Step #{step_num}"
71
+ end
72
+ output "\n"
73
+
74
+ num_steps = tutorial.num_steps
75
+ @litdiff.save!(tutorial) do |step_num|
76
+ output "\r\e[K[Tutorial -> doc/] Step #{step_num}/#{num_steps}"
77
+ end
78
+ output "\n"
79
+
80
+ @config.synced!
81
+ end
82
+
83
+ def litdiff_to_git!
84
+ tutorial = @litdiff.load! do |step_num|
85
+ output "\r\e[K[doc/ -> Tutorial] Step #{step_num}"
86
+ end
87
+ output "\n"
88
+
89
+ num_steps = tutorial.num_steps
90
+ @git.save!(tutorial) do |step_num|
91
+ output "\r\e[K[Tutorial -> repo/] Step #{step_num}/#{num_steps}"
92
+ end
93
+ output "\n"
94
+
95
+ @config.synced!
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,126 @@
1
+ module Leg
2
+ module Commands
3
+ class Build < BaseCommand
4
+ def self.name
5
+ "build"
6
+ end
7
+
8
+ def self.summary
9
+ "Render repo/ into an HTML or Markdown book."
10
+ end
11
+
12
+ def self.usage
13
+ "[-q]"
14
+ end
15
+
16
+ def setopts!(o)
17
+ o.on("-q", "--quiet", "Don't output progress") do |q|
18
+ @opts[:quiet] = q
19
+ end
20
+ end
21
+
22
+ def run
23
+ needs! :config, :repo
24
+
25
+ tutorial = @git.load!(full_diffs: true, diffs_ignore_whitespace: true) do |step_num|
26
+ output "\r\e[K[repo/ -> Tutorial] Step #{step_num}"
27
+ end
28
+ output "\n"
29
+
30
+ num_steps = tutorial.num_steps
31
+
32
+ if @config.options[:diff_transformers].nil?
33
+ @config.options[:diff_transformers] = [
34
+ { 'FoldSections' => {
35
+ unfold_before_new_section: true,
36
+ section_types: [
37
+ { name: 'comments', start: "^/\\*\\*\\*.+\\*\\*\\*/$", end: nil },
38
+ { name: 'braces', start: "^\\S.*{$", end: "^}( \\w+)?;?$" }
39
+ ]
40
+ }},
41
+ 'TrimBlankLines',
42
+ 'OmitAdjacentRemovals'
43
+ ]
44
+ end
45
+
46
+ if @config.options[:diff_transformers]
47
+ transformers = @config.options[:diff_transformers].map do |transformer_config|
48
+ if transformer_config.is_a? String
49
+ transformer = transformer_config
50
+ options = {}
51
+ else
52
+ transformer = transformer_config.keys.first
53
+ options = transformer_config.values.first
54
+ end
55
+ Leg::DiffTransformers.const_get(transformer).new(options)
56
+ end
57
+
58
+ tutorial.transform_diffs(transformers) do |step_num|
59
+ output "\r\e[K[Transform diffs] Step #{step_num}/#{num_steps}"
60
+ end
61
+ output "\n"
62
+ end
63
+
64
+ templates = Dir[File.join(@config.path, "template{,-?*}")].map do |template_dir|
65
+ [template_dir, File.basename(template_dir).split("-")[1] || "html"]
66
+ end
67
+ if templates.empty?
68
+ templates = [[nil, "html"], [nil, "md"]]
69
+ end
70
+
71
+ FileUtils.rm_rf(File.join(@config.path, "build"))
72
+ templates.each do |template_dir, format|
73
+ FileUtils.cd(@config.path) do
74
+ FileUtils.mkdir_p("build/#{format}")
75
+
76
+ include_default_css = (format == "html")
77
+ page_template = Leg::DefaultTemplates::PAGE[format]
78
+ if template_dir && File.exist?(File.join(template_dir, "page.#{format}.erb"))
79
+ page_template = File.read(File.join(template_dir, "page.#{format}.erb"))
80
+ include_default_css = false
81
+ end
82
+ page_template.gsub!(/\\\s*/, "")
83
+
84
+ step_template = Leg::DefaultTemplates::STEP[format]
85
+ if template_dir && File.exist?(File.join(template_dir, "step.#{format}.erb"))
86
+ step_template = File.read(File.join(template_dir, "step.#{format}.erb"))
87
+ end
88
+ step_template.gsub!(/\\\s*/, "")
89
+
90
+ tutorial.pages.each do |page|
91
+ output "\r\e[K[Tutorial -> build/] Page #{page.filename}"
92
+
93
+ content = Leg::Template.render_page(page_template, step_template, format, page, tutorial, @config)
94
+ File.write("build/#{format}/#{page.filename}.#{format}", content)
95
+ end
96
+ output "\n"
97
+
98
+ if template_dir
99
+ FileUtils.cd(template_dir) do
100
+ Dir["*"].each do |f|
101
+ name = File.basename(f)
102
+
103
+ next if ["page.#{format}.erb", "step.#{format}.erb"].include? name
104
+ next if name.start_with? "_"
105
+
106
+ # XXX: currently only processes top-level ERB template files.
107
+ if name.end_with? ".erb"
108
+ content = Leg::Template.render(File.read(f), tutorial, @config)
109
+ File.write("../build/#{format}/#{name[0..-5]}", content)
110
+ else
111
+ FileUtils.cp_r(f, "../build/#{format}/#{name}")
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ if include_default_css && !File.exist?("build/#{format}/style.css")
118
+ content = Leg::Template.render(Leg::DefaultTemplates::CSS, tutorial, @config)
119
+ File.write("build/#{format}/style.css", content)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,48 @@
1
+ module Leg
2
+ module Commands
3
+ class Commit < BaseCommand
4
+ def self.name
5
+ "commit"
6
+ end
7
+
8
+ def self.summary
9
+ "Append or insert a new step."
10
+ end
11
+
12
+ def self.usage
13
+ "[-s]"
14
+ end
15
+
16
+ def setopts!(o)
17
+ o.on("-m", "--message MESSAGE", "Set the step summary to MESSAGE") do |m|
18
+ @opts[:message] = m
19
+ end
20
+ o.on("-d", "--default-message", "Leave the step summary unchanged, or set it to a default if empty") do |d|
21
+ @opts[:default_message] = d
22
+ end
23
+ o.on("-s", "--stay", "Don't resolve rest of steps yet") do |s|
24
+ @opts[:stay] = s
25
+ end
26
+ end
27
+
28
+ def run
29
+ needs! :config, :repo
30
+
31
+ commit_options = {
32
+ no_rebase: @opts[:stay],
33
+ message: @opts[:message],
34
+ use_default_message: @opts[:default_message]
35
+ }
36
+
37
+ if @git.commit!(commit_options)
38
+ unless @opts[:stay]
39
+ git_to_litdiff!
40
+ output "Success!\n"
41
+ end
42
+ else
43
+ output "Looks like you've got a conflict to resolve!\n"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end