leg 0.0.1 → 0.0.2

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