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/lib/leg/diff.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
module Leg
|
2
|
+
class Diff
|
3
|
+
attr_accessor :filename, :is_new_file, :lines
|
4
|
+
|
5
|
+
def initialize(filename = nil, is_new_file = false, lines = [])
|
6
|
+
@filename = filename
|
7
|
+
@is_new_file = is_new_file
|
8
|
+
@lines = lines
|
9
|
+
end
|
10
|
+
|
11
|
+
def clone
|
12
|
+
Leg::Diff.new(@filename.dup, @is_new_file, @lines.map(&:clone))
|
13
|
+
end
|
14
|
+
|
15
|
+
def clone_empty
|
16
|
+
Leg::Diff.new(@filename.dup, @is_new_file, [])
|
17
|
+
end
|
18
|
+
|
19
|
+
# Append a Line to the Diff.
|
20
|
+
def <<(line)
|
21
|
+
unless line.is_a? Leg::Line
|
22
|
+
raise ArgumentError, "expected a Line"
|
23
|
+
end
|
24
|
+
@lines << line
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_patch(options = {})
|
29
|
+
patch = ""
|
30
|
+
patch += "diff --git a/#{@filename} b/#{@filename}\n" unless options[:strip_git_lines]
|
31
|
+
if @is_new_file
|
32
|
+
patch += "new file mode 100644\n" unless options[:strip_git_lines]
|
33
|
+
patch += "--- /dev/null\n"
|
34
|
+
else
|
35
|
+
patch += "--- #{'a/' unless options[:strip_git_lines]}#{@filename}\n"
|
36
|
+
end
|
37
|
+
patch += "+++ #{'b/' unless options[:strip_git_lines]}#{@filename}\n"
|
38
|
+
|
39
|
+
find_hunks.each do |hunk|
|
40
|
+
patch += hunk_header(hunk)
|
41
|
+
hunk.each do |line|
|
42
|
+
patch += line.to_patch(options)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
patch
|
47
|
+
end
|
48
|
+
|
49
|
+
# Parse a git diff and return an array of Diff objects, one for each file in
|
50
|
+
# the git diff.
|
51
|
+
def self.parse(git_diff)
|
52
|
+
in_diff = false
|
53
|
+
old_line_num = nil
|
54
|
+
new_line_num = nil
|
55
|
+
cur_diff = nil
|
56
|
+
diffs = []
|
57
|
+
|
58
|
+
git_diff.lines.each do |line|
|
59
|
+
if line =~ /^--- (.+)$/
|
60
|
+
cur_diff = Leg::Diff.new
|
61
|
+
if $1 == '/dev/null'
|
62
|
+
cur_diff.is_new_file = true
|
63
|
+
end
|
64
|
+
diffs << cur_diff
|
65
|
+
in_diff = false
|
66
|
+
elsif line =~ /^\+\+\+ (.+)$/
|
67
|
+
cur_diff.filename = $1.strip.sub(/^b\//, '')
|
68
|
+
elsif line =~ /^@@ -(\d+)(,\d+)? \+(\d+)(,\d+)? @@/
|
69
|
+
# TODO: somehow preserve function name that comes to the right of the @@ header?
|
70
|
+
in_diff = true
|
71
|
+
old_line_num = $1.to_i
|
72
|
+
new_line_num = $3.to_i
|
73
|
+
elsif in_diff && line[0] == '\\'
|
74
|
+
# Ignore "".
|
75
|
+
elsif in_diff && [' ', '|', '+', '-'].include?(line[0])
|
76
|
+
case line[0]
|
77
|
+
when ' ', '|'
|
78
|
+
line_nums = [old_line_num, new_line_num]
|
79
|
+
old_line_num += 1
|
80
|
+
new_line_num += 1
|
81
|
+
cur_diff << Leg::Line::Unchanged.new(line[1..-1], line_nums)
|
82
|
+
when '+'
|
83
|
+
line_nums = [nil, new_line_num]
|
84
|
+
new_line_num += 1
|
85
|
+
cur_diff << Leg::Line::Added.new(line[1..-1], line_nums)
|
86
|
+
when '-'
|
87
|
+
line_nums = [old_line_num, nil]
|
88
|
+
old_line_num += 1
|
89
|
+
cur_diff << Leg::Line::Removed.new(line[1..-1], line_nums)
|
90
|
+
end
|
91
|
+
else
|
92
|
+
in_diff = false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
diffs
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
# :S
|
102
|
+
def hunk_header(hunk)
|
103
|
+
old_line, new_line = hunk.first.line_numbers
|
104
|
+
old_line ||= 1
|
105
|
+
new_line ||= 1
|
106
|
+
|
107
|
+
old_count = hunk.count { |line| [Leg::Line::Removed, Leg::Line::Unchanged].include? line.class }
|
108
|
+
new_count = hunk.count { |line| [Leg::Line::Added, Leg::Line::Unchanged].include? line.class }
|
109
|
+
|
110
|
+
old_line = 0 if old_count == 0
|
111
|
+
new_line = 0 if new_count == 0
|
112
|
+
|
113
|
+
"@@ -#{old_line},#{old_count} +#{new_line},#{new_count} @@\n"
|
114
|
+
end
|
115
|
+
|
116
|
+
# :(
|
117
|
+
def find_hunks
|
118
|
+
raise "can't create patch from empty diff" if @lines.empty?
|
119
|
+
hunks = []
|
120
|
+
cur_hunk = [@lines.first]
|
121
|
+
cur_line_nums = @lines.first.line_numbers.dup
|
122
|
+
@lines[1..-1].each do |line|
|
123
|
+
case line
|
124
|
+
when Leg::Line::Unchanged
|
125
|
+
cur_line_nums[0] = cur_line_nums[0].nil? ? line.line_numbers[0] : (cur_line_nums[0] + 1)
|
126
|
+
cur_line_nums[1] = cur_line_nums[1].nil? ? line.line_numbers[1] : (cur_line_nums[1] + 1)
|
127
|
+
when Leg::Line::Added
|
128
|
+
cur_line_nums[1] = cur_line_nums[1].nil? ? line.line_numbers[1] : (cur_line_nums[1] + 1)
|
129
|
+
when Leg::Line::Removed
|
130
|
+
cur_line_nums[0] = cur_line_nums[0].nil? ? line.line_numbers[0] : (cur_line_nums[0] + 1)
|
131
|
+
when Leg::Line::Folded
|
132
|
+
raise "can't create patch from diff with folded lines"
|
133
|
+
end
|
134
|
+
|
135
|
+
old_match = (line.line_numbers[0].nil? || line.line_numbers[0] == cur_line_nums[0])
|
136
|
+
new_match = (line.line_numbers[1].nil? || line.line_numbers[1] == cur_line_nums[1])
|
137
|
+
|
138
|
+
if !old_match || !new_match
|
139
|
+
hunks << cur_hunk
|
140
|
+
|
141
|
+
cur_hunk = []
|
142
|
+
cur_line_nums = line.line_numbers.dup
|
143
|
+
end
|
144
|
+
|
145
|
+
cur_hunk << line
|
146
|
+
end
|
147
|
+
hunks << cur_hunk
|
148
|
+
hunks
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'leg/diff_transformers/base_transformer'
|
2
|
+
|
3
|
+
require 'leg/diff_transformers/fold_sections'
|
4
|
+
require 'leg/diff_transformers/omit_adjacent_removals'
|
5
|
+
require 'leg/diff_transformers/syntax_highlight'
|
6
|
+
require 'leg/diff_transformers/trim_blank_lines'
|
7
|
+
|
8
|
+
module Leg
|
9
|
+
module DiffTransformers
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Leg
|
2
|
+
module DiffTransformers
|
3
|
+
class FoldSections < BaseTransformer
|
4
|
+
def transform(diff)
|
5
|
+
sections = @options[:section_types].map { [] }
|
6
|
+
|
7
|
+
cur_sections = @options[:section_types].map { nil }
|
8
|
+
diff.lines.each.with_index do |line, idx|
|
9
|
+
@options[:section_types].each.with_index do |section_type, level|
|
10
|
+
if line.source =~ Regexp.new(section_type[:start])
|
11
|
+
if !section_type[:end] && cur_sections[level]
|
12
|
+
cur_sections[level].end_line = idx - 1
|
13
|
+
if @options[:unfold_before_new_section]
|
14
|
+
cur_sections[level].dirty! if [:added, :removed].include? line.type
|
15
|
+
end
|
16
|
+
sections[level] << cur_sections[level]
|
17
|
+
end
|
18
|
+
|
19
|
+
cur_sections[level] = Section.new(level, idx)
|
20
|
+
|
21
|
+
if [:added, :removed].include? line.type
|
22
|
+
cur_sections[level].dirty!
|
23
|
+
end
|
24
|
+
elsif section_type[:end] && line.source =~ Regexp.new(section_type[:end])
|
25
|
+
if [:added, :removed].include? line.type
|
26
|
+
cur_sections[level].dirty!
|
27
|
+
end
|
28
|
+
|
29
|
+
cur_sections[level].end_line = idx
|
30
|
+
sections[level] << cur_sections[level]
|
31
|
+
cur_sections[level] = nil
|
32
|
+
elsif cur_sections[level]
|
33
|
+
if [:added, :removed].include? line.type
|
34
|
+
cur_sections[level].dirty!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
cur_sections.each.with_index do |section, level|
|
40
|
+
unless section.nil?
|
41
|
+
section.end_line = diff.lines.length - 1
|
42
|
+
sections[level] << section
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
new_diff = diff.clone
|
47
|
+
sections.each.with_index do |level_sections, level|
|
48
|
+
level_sections.each do |section|
|
49
|
+
if !section.dirty? && !new_diff.lines[section.to_range].any?(&:nil?)
|
50
|
+
start_line = new_diff.lines[section.start_line]
|
51
|
+
end_line = new_diff.lines[section.end_line]
|
52
|
+
|
53
|
+
summary_lines = [start_line]
|
54
|
+
summary_lines << end_line if @options[:section_types][level][:end]
|
55
|
+
summary = summary_lines.map(&:source).join(" … ")
|
56
|
+
|
57
|
+
line_numbers = [start_line.line_number, end_line.line_number]
|
58
|
+
|
59
|
+
folded_line = Leg::Line::Folded.new(summary, line_numbers)
|
60
|
+
|
61
|
+
section.to_range.each do |idx|
|
62
|
+
new_diff.lines[idx] = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
new_diff.lines[section.start_line] = folded_line
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
new_diff.lines.compact!
|
70
|
+
new_diff
|
71
|
+
end
|
72
|
+
|
73
|
+
class Section
|
74
|
+
attr_accessor :level, :start_line, :end_line, :dirty
|
75
|
+
|
76
|
+
def initialize(level, start_line, end_line = nil, dirty = false)
|
77
|
+
@level, @start_line, @end_line, @dirty = level, start_line, end_line, dirty
|
78
|
+
end
|
79
|
+
|
80
|
+
def to_range
|
81
|
+
start_line..end_line
|
82
|
+
end
|
83
|
+
|
84
|
+
def dirty?; @dirty; end
|
85
|
+
def dirty!; @dirty = true; end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Leg
|
2
|
+
module DiffTransformers
|
3
|
+
class OmitAdjacentRemovals < BaseTransformer
|
4
|
+
def transform(diff)
|
5
|
+
new_diff = diff.clone
|
6
|
+
|
7
|
+
removed_lines = []
|
8
|
+
saw_added_line = false
|
9
|
+
new_diff.lines.each.with_index do |line, idx|
|
10
|
+
case line.type
|
11
|
+
when :unchanged, :folded
|
12
|
+
if saw_added_line
|
13
|
+
removed_lines.each do |removed_idx|
|
14
|
+
new_diff.lines[removed_idx] = nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
removed_lines = []
|
19
|
+
saw_added_line = false
|
20
|
+
when :added
|
21
|
+
saw_added_line = true
|
22
|
+
when :removed
|
23
|
+
removed_lines << idx
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
if saw_added_line
|
28
|
+
removed_lines.each do |removed_idx|
|
29
|
+
new_diff.lines[removed_idx] = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
new_diff.lines.compact!
|
34
|
+
new_diff
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Leg
|
2
|
+
module DiffTransformers
|
3
|
+
class SyntaxHighlight < BaseTransformer
|
4
|
+
class HTMLLineByLine < Rouge::Formatter
|
5
|
+
def initialize(formatter)
|
6
|
+
@formatter = formatter
|
7
|
+
end
|
8
|
+
|
9
|
+
def stream(tokens, &b)
|
10
|
+
token_lines(tokens) do |line|
|
11
|
+
line.each do |tok, val|
|
12
|
+
yield @formatter.span(tok, val)
|
13
|
+
end
|
14
|
+
yield "\n"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
SYNTAX_HIGHLIGHTER = HTMLLineByLine.new(Rouge::Formatters::HTML.new)
|
20
|
+
|
21
|
+
def transform(diff)
|
22
|
+
new_diff = diff.clone
|
23
|
+
code = new_diff.lines.map(&:source).join("\n") + "\n"
|
24
|
+
lexer = Rouge::Lexer.guess(filename: new_diff.filename, source: code)
|
25
|
+
SYNTAX_HIGHLIGHTER.format(lexer.lex(code)).lines.each.with_index do |line_hl, idx|
|
26
|
+
new_diff.lines[idx].source = line_hl
|
27
|
+
end
|
28
|
+
new_diff
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Leg
|
2
|
+
module DiffTransformers
|
3
|
+
class TrimBlankLines < BaseTransformer
|
4
|
+
def transform(diff)
|
5
|
+
new_diff = diff.clone_empty
|
6
|
+
diff.lines.each.with_index do |line, idx|
|
7
|
+
line = line.clone
|
8
|
+
if line.blank? && [:added, :removed].include?(line.type)
|
9
|
+
prev_line = idx > 0 ? diff.lines[idx - 1] : nil
|
10
|
+
next_line = idx < diff.lines.length - 1 ? diff.lines[idx + 1] : nil
|
11
|
+
|
12
|
+
prev_changed = prev_line && [:added, :removed].include?(prev_line.type)
|
13
|
+
next_changed = next_line && [:added, :removed].include?(next_line.type)
|
14
|
+
|
15
|
+
if !prev_changed || !next_changed
|
16
|
+
line = Leg::Line::Unchanged.new(line.source, line.line_numbers)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
new_diff.lines << line
|
20
|
+
end
|
21
|
+
new_diff
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/leg/line.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
module Leg
|
2
|
+
class Line
|
3
|
+
attr_accessor :source, :line_numbers
|
4
|
+
|
5
|
+
def initialize(source, line_numbers)
|
6
|
+
@source = source.chomp
|
7
|
+
@line_numbers = line_numbers
|
8
|
+
end
|
9
|
+
|
10
|
+
def clone
|
11
|
+
self.class.new(@source.dup, @line_numbers.dup)
|
12
|
+
end
|
13
|
+
|
14
|
+
def blank?
|
15
|
+
@source.strip.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def line_number
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_patch(options = {})
|
23
|
+
raise NotImplementedError
|
24
|
+
end
|
25
|
+
|
26
|
+
class Added < Line
|
27
|
+
def type
|
28
|
+
:added
|
29
|
+
end
|
30
|
+
|
31
|
+
def line_number
|
32
|
+
@line_numbers[1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_patch(options = {})
|
36
|
+
"+#{@source}\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Removed < Line
|
41
|
+
def type
|
42
|
+
:removed
|
43
|
+
end
|
44
|
+
|
45
|
+
def line_number
|
46
|
+
@line_numbers[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_patch(options = {})
|
50
|
+
"-#{@source}\n"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Unchanged < Line
|
55
|
+
def type
|
56
|
+
:unchanged
|
57
|
+
end
|
58
|
+
|
59
|
+
def line_number
|
60
|
+
@line_numbers[1]
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_patch(options = {})
|
64
|
+
char = options[:unchanged_char] || " "
|
65
|
+
"#{char}#{@source}\n"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Folded < Line
|
70
|
+
def type
|
71
|
+
:folded
|
72
|
+
end
|
73
|
+
|
74
|
+
def line_number
|
75
|
+
@line_numbers[0]
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_patch(options = {})
|
79
|
+
raise "can't convert folded line to patch"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|