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.
- 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
|