leg 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +10 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +34 -0
- data/LICENSE +21 -0
- data/README.md +59 -0
- data/Rakefile +11 -0
- data/TUTORIAL.md +243 -0
- data/bin/console +9 -0
- data/bin/setup +6 -0
- data/exe/leg +6 -0
- data/leg.gemspec +31 -0
- data/lib/leg.rb +27 -0
- data/lib/leg/cli.rb +42 -0
- data/lib/leg/commands.rb +19 -0
- data/lib/leg/commands/amend.rb +49 -0
- data/lib/leg/commands/base_command.rb +99 -0
- data/lib/leg/commands/build.rb +126 -0
- data/lib/leg/commands/commit.rb +48 -0
- data/lib/leg/commands/diff.rb +29 -0
- data/lib/leg/commands/help.rb +43 -0
- data/lib/leg/commands/init.rb +46 -0
- data/lib/leg/commands/reset.rb +26 -0
- data/lib/leg/commands/resolve.rb +31 -0
- data/lib/leg/commands/save.rb +31 -0
- data/lib/leg/commands/status.rb +54 -0
- data/lib/leg/commands/step.rb +31 -0
- data/lib/leg/config.rb +42 -0
- data/lib/leg/default_templates.rb +340 -0
- data/lib/leg/diff.rb +151 -0
- data/lib/leg/diff_transformers.rb +11 -0
- data/lib/leg/diff_transformers/base_transformer.rb +13 -0
- data/lib/leg/diff_transformers/fold_sections.rb +89 -0
- data/lib/leg/diff_transformers/omit_adjacent_removals.rb +38 -0
- data/lib/leg/diff_transformers/syntax_highlight.rb +32 -0
- data/lib/leg/diff_transformers/trim_blank_lines.rb +25 -0
- data/lib/leg/line.rb +83 -0
- data/lib/leg/markdown.rb +20 -0
- data/lib/leg/page.rb +27 -0
- data/lib/leg/representations.rb +9 -0
- data/lib/leg/representations/base_representation.rb +42 -0
- data/lib/leg/representations/git.rb +388 -0
- data/lib/leg/representations/litdiff.rb +85 -0
- data/lib/leg/step.rb +16 -0
- data/lib/leg/template.rb +95 -0
- data/lib/leg/tutorial.rb +49 -0
- data/lib/leg/version.rb +3 -0
- metadata +112 -38
- data/bin/leg +0 -9
- data/lib/snaptoken.rb +0 -24
- data/lib/snaptoken/cli.rb +0 -61
- data/lib/snaptoken/commands.rb +0 -13
- data/lib/snaptoken/commands/amend.rb +0 -27
- data/lib/snaptoken/commands/base_command.rb +0 -92
- data/lib/snaptoken/commands/build.rb +0 -107
- data/lib/snaptoken/commands/commit.rb +0 -27
- data/lib/snaptoken/commands/help.rb +0 -38
- data/lib/snaptoken/commands/resolve.rb +0 -27
- data/lib/snaptoken/commands/status.rb +0 -21
- data/lib/snaptoken/commands/step.rb +0 -35
- data/lib/snaptoken/default_templates.rb +0 -287
- data/lib/snaptoken/diff.rb +0 -180
- data/lib/snaptoken/diff_line.rb +0 -54
- data/lib/snaptoken/diff_transformers.rb +0 -9
- data/lib/snaptoken/diff_transformers/base_transformer.rb +0 -9
- data/lib/snaptoken/diff_transformers/fold_sections.rb +0 -85
- data/lib/snaptoken/diff_transformers/omit_adjacent_removals.rb +0 -28
- data/lib/snaptoken/diff_transformers/trim_blank_lines.rb +0 -21
- data/lib/snaptoken/markdown.rb +0 -18
- data/lib/snaptoken/page.rb +0 -64
- data/lib/snaptoken/representations.rb +0 -8
- data/lib/snaptoken/representations/base_representation.rb +0 -38
- data/lib/snaptoken/representations/git.rb +0 -262
- data/lib/snaptoken/representations/litdiff.rb +0 -81
- data/lib/snaptoken/step.rb +0 -27
- data/lib/snaptoken/template.rb +0 -53
- data/lib/snaptoken/tutorial.rb +0 -64
data/bin/console
ADDED
data/bin/setup
ADDED
data/exe/leg
ADDED
data/leg.gemspec
ADDED
@@ -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
|
data/lib/leg.rb
ADDED
@@ -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
|
data/lib/leg/cli.rb
ADDED
@@ -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
|
data/lib/leg/commands.rb
ADDED
@@ -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
|