leg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,21 @@
1
+ class Snaptoken::Commands::Status < Snaptoken::Commands::BaseCommand
2
+ def self.name
3
+ "status"
4
+ end
5
+
6
+ def self.summary
7
+ "Show whether doc/ and repo/ were\n" +
8
+ "last modified since the last sync."
9
+ end
10
+
11
+ def self.usage
12
+ ""
13
+ end
14
+
15
+ def setopts!(o)
16
+ end
17
+
18
+ def run
19
+ needs! :config
20
+ end
21
+ end
@@ -0,0 +1,35 @@
1
+ class Snaptoken::Commands::Step < Snaptoken::Commands::BaseCommand
2
+ def self.name
3
+ "step"
4
+ end
5
+
6
+ def self.summary
7
+ "Select a step for editing."
8
+ end
9
+
10
+ def self.usage
11
+ "<step-number>"
12
+ end
13
+
14
+ def setopts!(o)
15
+ end
16
+
17
+ def run
18
+ needs! :config, :repo
19
+
20
+ step_number = @args.first.to_i
21
+
22
+ FileUtils.cd(@git.repo_path) do
23
+ @git.each_step do |cur_step, commit|
24
+ if cur_step == step_number
25
+ `git checkout #{commit.oid}`
26
+ @git.copy_repo_to_step!
27
+ exit
28
+ end
29
+ end
30
+
31
+ puts "Error: Step not found."
32
+ exit 1
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,287 @@
1
+ module Snaptoken::DefaultTemplates
2
+ PAGE = <<~TEMPLATE
3
+ <!doctype html>
4
+ <html>
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
8
+ <title><%= page_number %>. <%= page_title %></title>
9
+ <link href="style.css" rel="stylesheet">
10
+ </head>
11
+ <body>
12
+ <header class="bar">
13
+ <nav>
14
+ <% if prev_page %>
15
+ <a href="<%= prev_page.filename %>.html">&larr; prev</a>
16
+ <% else %>
17
+ <a href="#"></a>
18
+ <% end %>
19
+
20
+ <a href="<%= pages.first.filename %>.html">beginning</a>
21
+
22
+ <% if next_page %>
23
+ <a href="<%= next_page.filename %>.html">next &rarr;</a>
24
+ <% else %>
25
+ <a href="#"></a>
26
+ <% end %>
27
+ </nav>
28
+ </header>
29
+ <div id="container">
30
+ <%= content %>
31
+ </div>
32
+ <footer class="bar">
33
+ <nav>
34
+ <a href="#">top of page</a>
35
+ </nav>
36
+ </footer>
37
+ </body>
38
+ </html>
39
+ TEMPLATE
40
+
41
+ STEP = <<~TEMPLATE
42
+ <% for diff in diffs %>
43
+ <div class="diff">
44
+ <div class="diff-header">
45
+ <div class="step-filename">
46
+ <%= diff.filename %>
47
+ </div>
48
+ <div class="step-number">
49
+ Step <%= number %>
50
+ </div>
51
+ </div>
52
+ <pre class="highlight"><code>\\
53
+ <% for line in diff.lines %>\\
54
+ <% if line.type == :folded %>\\
55
+ <div class="line folded">\\
56
+ <%= line.source.gsub('<span class="err">…</span>', '…') %>\\
57
+ </div>\\
58
+ <% else %>\\
59
+ <% tag = {unchanged: :div, added: :ins, removed: :del}[line.type] %>\\
60
+ <% tag = :div if diff.is_new_file %>\\
61
+ <<%= tag %> class="line">\\
62
+ <%= line.source %>\\
63
+ </<%= tag %>>\\
64
+ <% end %>\\
65
+ <% end %>\\
66
+ </code></pre>
67
+ </div>
68
+ <% end %>
69
+ TEMPLATE
70
+
71
+ CSS = <<~TEMPLATE
72
+ * {
73
+ margin: 0;
74
+ padding: 0;
75
+ box-sizing: border-box;
76
+ }
77
+
78
+ body {
79
+ font-family: Utopia, Georgia, Times, 'Apple Symbols', serif;
80
+ line-height: 140%;
81
+ color: #333;
82
+ font-size: 18px;
83
+ }
84
+
85
+ #container {
86
+ width: 700px;
87
+ margin: 18px auto;
88
+ }
89
+
90
+ .bar {
91
+ display: block;
92
+ width: 100%;
93
+ background-color: #ceb;
94
+ box-shadow: 0px 0px 15px 1px #ddd;
95
+ }
96
+
97
+ .bar > nav {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ width: 700px;
101
+ margin: 0 auto;
102
+ }
103
+
104
+ footer.bar > nav {
105
+ justify-content: center;
106
+ }
107
+
108
+ .bar > nav > a {
109
+ display: block;
110
+ padding: 2px 0 4px 0;
111
+ color: #152;
112
+ }
113
+
114
+ h1, h2, h3, h4, h5, h6 {
115
+ font-family: Futura, Helvetica, Arial, sans-serif;
116
+ color: #222;
117
+ line-height: 100%;
118
+ margin-top: 32px;
119
+ }
120
+
121
+ h2 a, h3 a, h4 a {
122
+ color: inherit;
123
+ text-decoration: none;
124
+ }
125
+
126
+ h2 a::before, h3 a::before, h4 a::before {
127
+ content: '#';
128
+ color: #fff;
129
+ font-weight: normal;
130
+ transition: color 0.15s ease;
131
+ display: block;
132
+ float: left;
133
+ width: 32px;
134
+ margin-left: -32px;
135
+ }
136
+
137
+ h2 a:hover::before, h3 a:hover::before, h4 a:hover::before {
138
+ color: #ccc;
139
+ }
140
+
141
+ h1 {
142
+ margin-top: 0;
143
+ font-size: 38px;
144
+ border-bottom: 3px solid #e7c;
145
+ display: inline-block;
146
+ }
147
+
148
+ h2 {
149
+ font-size: 26px;
150
+ }
151
+
152
+ p {
153
+ margin-top: 18px;
154
+ }
155
+
156
+ ul, ol {
157
+ margin-top: 18px;
158
+ margin-left: 36px;
159
+ }
160
+
161
+ hr {
162
+ border: none;
163
+ border-bottom: 1px solid #888;
164
+ }
165
+
166
+ a {
167
+ color: #26d;
168
+ }
169
+
170
+ code {
171
+ font-family: monospace;
172
+ font-size: inherit;
173
+ white-space: nowrap;
174
+ background-color: #eff4ea;
175
+ padding: 1px 3px;
176
+ }
177
+
178
+ h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
179
+ font-weight: normal;
180
+ }
181
+
182
+ kbd {
183
+ font-family: monospace;
184
+ border-radius: 3px;
185
+ padding: 2px 3px;
186
+ box-shadow: 1px 1px 1px #777;
187
+ margin: 2px;
188
+ font-size: 14px;
189
+ background: #f7f7f7;
190
+ font-weight: 500;
191
+ color: #555;
192
+ white-space: nowrap;
193
+ }
194
+
195
+ h1 kbd, h2 kbd, h3 kbd, h4 kbd, h5 kbd, h6 kbd {
196
+ font-size: 80%;
197
+ }
198
+
199
+ .diff code {
200
+ font-size: 14px;
201
+ line-height: 20px;
202
+ background-color: none;
203
+ padding: 0;
204
+ margin-bottom: 18px;
205
+ white-space: inherit;
206
+ }
207
+
208
+ .diff pre {
209
+ background-color: #fffcfa;
210
+ padding: 5px 0;
211
+ }
212
+
213
+ .diff {
214
+ border: 1px solid #ede7e3;
215
+ border-radius: 3px;
216
+ margin-top: 18px;
217
+ }
218
+
219
+ .diff .diff-header {
220
+ display: flex;
221
+ justify-content: space-between;
222
+ padding: 0 5px;
223
+ background-color: #ede7e3;
224
+ font-size: 16px;
225
+ color: #666;
226
+ }
227
+
228
+ .diff .step-number {
229
+ font-weight: bold;
230
+ }
231
+
232
+ .diff .step-filename {
233
+ font-weight: bold;
234
+ }
235
+
236
+ .diff .step-name {
237
+ font-family: monospace;
238
+ font-size: 12px;
239
+ }
240
+
241
+ .diff .line {
242
+ display: block;
243
+ height: 20px;
244
+ padding: 0 5px;
245
+ position: relative;
246
+ }
247
+
248
+ .diff .line.folded {
249
+ background-color: #eef;
250
+ opacity: 0.5;
251
+ }
252
+
253
+ .diff ins.line {
254
+ background-color: #ffd;
255
+ text-decoration: none;
256
+ }
257
+
258
+ .diff del.line {
259
+ background-color: #fdd;
260
+ text-decoration: line-through;
261
+ }
262
+
263
+ @media screen and (max-width: 700px) {
264
+ #container {
265
+ width: auto;
266
+ margin: 18px 0;
267
+ padding: 0 5px;
268
+ }
269
+
270
+ .bar > nav {
271
+ width: auto;
272
+ margin: 0;
273
+ padding: 0 5px;
274
+ }
275
+
276
+ .highlight {
277
+ overflow-x: scroll;
278
+ }
279
+
280
+ .diff .line {
281
+ width: 700px;
282
+ }
283
+ }
284
+
285
+ <%= syntax_highlighting_css ".highlight" %>
286
+ TEMPLATE
287
+ end
@@ -0,0 +1,180 @@
1
+ class Snaptoken::Diff
2
+ attr_accessor :filename, :is_new_file, :lines, :syntax_highlighted
3
+
4
+ def initialize(filename = nil, is_new_file = false, lines = [])
5
+ @filename = filename
6
+ @is_new_file = is_new_file
7
+ @lines = lines
8
+ @syntax_highlighted = false
9
+ end
10
+
11
+ def clone
12
+ diff = Snaptoken::Diff.new(@filename.dup, @is_new_file, @lines.map(&:clone))
13
+ diff.syntax_highlighted = @syntax_highlighted
14
+ diff
15
+ end
16
+
17
+ def clone_empty
18
+ diff = Snaptoken::Diff.new(@filename.dup, @is_new_file, [])
19
+ diff.syntax_highlighted = @syntax_highlighted
20
+ diff
21
+ end
22
+
23
+ # Append a DiffLine to the Diff.
24
+ def <<(diff_line)
25
+ unless diff_line.is_a? Snaptoken::DiffLine
26
+ raise ArgumentError, "expected a DiffLine"
27
+ end
28
+ @lines << diff_line
29
+ self
30
+ end
31
+
32
+ def to_patch(options = {})
33
+ patch = "diff --git a/#{@filename} b/#{@filename}\n"
34
+ if @is_new_file
35
+ patch += "new file mode 100644\n"
36
+ patch += "--- /dev/null\n"
37
+ else
38
+ patch += "--- a/#{@filename}\n"
39
+ end
40
+ patch += "+++ b/#{@filename}\n"
41
+
42
+ find_hunks.each do |hunk|
43
+ patch += hunk_header(hunk)
44
+ hunk.each do |line|
45
+ patch += line.to_patch(options)
46
+ end
47
+ end
48
+
49
+ patch
50
+ end
51
+
52
+ # Parse a git diff and return an array of Diff objects, one for each file in
53
+ # the git diff.
54
+ def self.parse(git_diff)
55
+ in_diff = false
56
+ old_line_num = nil
57
+ new_line_num = nil
58
+ cur_diff = nil
59
+ diffs = []
60
+
61
+ git_diff.lines.each do |line|
62
+ if line =~ /^diff --git (\S+) (\S+)$/
63
+ filename = $2.split("/")[1..-1].join("/")
64
+ cur_diff = Snaptoken::Diff.new(filename)
65
+ diffs << cur_diff
66
+ in_diff = false
67
+ elsif !in_diff && line.start_with?('new file')
68
+ cur_diff.is_new_file = true
69
+ elsif line =~ /^@@ -(\d+)(,\d+)? \+(\d+)(,\d+)? @@/
70
+ # TODO: somehow preserve function name that comes to the right of the @@ header?
71
+ in_diff = true
72
+ old_line_num = $1.to_i
73
+ new_line_num = $3.to_i
74
+ elsif in_diff && line[0] == '\\'
75
+ # Ignore "".
76
+ elsif in_diff && [' ', '|', '+', '-'].include?(line[0])
77
+ case line[0]
78
+ when ' ', '|'
79
+ type = :unchanged
80
+ line_nums = [old_line_num, new_line_num]
81
+ old_line_num += 1
82
+ new_line_num += 1
83
+ when '+'
84
+ type = :added
85
+ line_nums = [nil, new_line_num]
86
+ new_line_num += 1
87
+ when '-'
88
+ type = :removed
89
+ line_nums = [old_line_num, nil]
90
+ old_line_num += 1
91
+ end
92
+
93
+ cur_diff << Snaptoken::DiffLine.new(type, line[1..-1], line_nums)
94
+ else
95
+ in_diff = false
96
+ end
97
+ end
98
+
99
+ diffs
100
+ end
101
+
102
+ class HTMLLineByLine < Rouge::Formatter
103
+ def initialize(formatter)
104
+ @formatter = formatter
105
+ end
106
+
107
+ def stream(tokens, &b)
108
+ token_lines(tokens) do |line|
109
+ line.each do |tok, val|
110
+ yield @formatter.span(tok, val)
111
+ end
112
+ yield "\n"
113
+ end
114
+ end
115
+ end
116
+
117
+ SYNTAX_HIGHLIGHTER = HTMLLineByLine.new(Rouge::Formatters::HTML.new)
118
+
119
+ def syntax_highlight!
120
+ return if @syntax_highlighted
121
+ code = @lines.map(&:source).join("\n") + "\n"
122
+ lexer = Rouge::Lexer.guess(filename: @filename, source: code)
123
+ SYNTAX_HIGHLIGHTER.format(lexer.lex(code)).lines.each.with_index do |line_hl, idx|
124
+ @lines[idx].source = line_hl
125
+ end
126
+ @syntax_highlighted = true
127
+ end
128
+
129
+ private
130
+
131
+ # :S
132
+ def hunk_header(hunk)
133
+ old_line, new_line = hunk.first.line_numbers
134
+ old_line ||= 1
135
+ new_line ||= 1
136
+
137
+ old_count = hunk.count { |line| [:removed, :unchanged].include? line.type }
138
+ new_count = hunk.count { |line| [:added, :unchanged].include? line.type }
139
+
140
+ old_line = 0 if old_count == 0
141
+ new_line = 0 if new_count == 0
142
+
143
+ "@@ -#{old_line},#{old_count} +#{new_line},#{new_count} @@\n"
144
+ end
145
+
146
+ # :(
147
+ def find_hunks
148
+ raise "can't create patch from empty diff" if @lines.empty?
149
+ hunks = []
150
+ cur_hunk = [@lines.first]
151
+ cur_line_nums = @lines.first.line_numbers.dup
152
+ @lines[1..-1].each do |line|
153
+ case line.type
154
+ when :unchanged
155
+ cur_line_nums[0] = cur_line_nums[0].nil? ? line.line_numbers[0] : (cur_line_nums[0] + 1)
156
+ cur_line_nums[1] = cur_line_nums[1].nil? ? line.line_numbers[1] : (cur_line_nums[1] + 1)
157
+ when :added
158
+ cur_line_nums[1] = cur_line_nums[1].nil? ? line.line_numbers[1] : (cur_line_nums[1] + 1)
159
+ when :removed
160
+ cur_line_nums[0] = cur_line_nums[0].nil? ? line.line_numbers[0] : (cur_line_nums[0] + 1)
161
+ when :folded
162
+ raise "can't create patch from diff with folded lines"
163
+ end
164
+
165
+ old_match = (line.line_numbers[0].nil? || line.line_numbers[0] == cur_line_nums[0])
166
+ new_match = (line.line_numbers[1].nil? || line.line_numbers[1] == cur_line_nums[1])
167
+
168
+ if !old_match || !new_match
169
+ hunks << cur_hunk
170
+
171
+ cur_hunk = []
172
+ cur_line_nums = line.line_numbers.dup
173
+ end
174
+
175
+ cur_hunk << line
176
+ end
177
+ hunks << cur_hunk
178
+ hunks
179
+ end
180
+ end