snaptoken 0.10.0
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 +7 -0
- data/bin/leg +9 -0
- data/lib/snaptoken/cli.rb +46 -0
- data/lib/snaptoken/commands/base_command.rb +124 -0
- data/lib/snaptoken/commands/deploy.rb +33 -0
- data/lib/snaptoken/commands/diff.rb +37 -0
- data/lib/snaptoken/commands/doc.rb +128 -0
- data/lib/snaptoken/commands/fancy.rb +26 -0
- data/lib/snaptoken/commands/help.rb +20 -0
- data/lib/snaptoken/commands/pieces.rb +14 -0
- data/lib/snaptoken/commands/ref.rb +37 -0
- data/lib/snaptoken/commands/repo.rb +43 -0
- data/lib/snaptoken/commands/undiff.rb +58 -0
- data/lib/snaptoken/commands/unrepo.rb +37 -0
- data/lib/snaptoken/commands.rb +17 -0
- data/lib/snaptoken/diff.rb +201 -0
- data/lib/snaptoken.rb +14 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c560cdd80e151f13503cd318c9aec1dcfa029849
|
4
|
+
data.tar.gz: d950cc9f6aafd966680e21bd949f8d4b5efae81f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1de8a8e365f2ba54ca8db92d0042f801b1f9767a2359ee1fae68c849f8949b232aba3ec1e6f88f5d258fe712814279be63b7302218a885439819e91dabf0f2d9
|
7
|
+
data.tar.gz: 8249d015895a23feea437f0b69ffd0f0de8eeb0503b60934cb023f0a9ff66fbab243901da97f7c379f1ecab406a3626652c738fd4ee8c0fa7a798b8b77c0fd4c
|
data/bin/leg
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
class Snaptoken::CLI
|
2
|
+
CONFIG_FILE = "leg.yml"
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
initial_dir = FileUtils.pwd
|
6
|
+
|
7
|
+
last_dir = nil
|
8
|
+
last_dir2 = nil
|
9
|
+
while FileUtils.pwd != last_dir
|
10
|
+
if File.exist?(CONFIG_FILE)
|
11
|
+
@config = YAML.load(File.read(CONFIG_FILE))
|
12
|
+
unless @config.is_a? Hash
|
13
|
+
puts "Error: Invalid config file."
|
14
|
+
exit!
|
15
|
+
end
|
16
|
+
@config[:path] = FileUtils.pwd
|
17
|
+
@config[:step_path] = last_dir2
|
18
|
+
@config[:orig_path] = initial_dir
|
19
|
+
break
|
20
|
+
end
|
21
|
+
|
22
|
+
last_dir2 = last_dir
|
23
|
+
last_dir = FileUtils.pwd
|
24
|
+
FileUtils.cd('..')
|
25
|
+
end
|
26
|
+
|
27
|
+
FileUtils.cd(initial_dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
def run(args)
|
31
|
+
args = ["help"] if args.empty?
|
32
|
+
cmd_name = args.shift.downcase
|
33
|
+
|
34
|
+
if cmd_name =~ /\A\d+\z/
|
35
|
+
args.unshift(cmd_name)
|
36
|
+
cmd_name = "ref"
|
37
|
+
end
|
38
|
+
|
39
|
+
if cmd = Snaptoken::Commands::LIST.find { |cmd| cmd.name == cmd_name }
|
40
|
+
cmd.new(args, @config).run
|
41
|
+
else
|
42
|
+
puts "There is no '#{cmd_name}' command. Run `leg help` for help."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,124 @@
|
|
1
|
+
class Snaptoken::Commands::BaseCommand
|
2
|
+
def initialize(args, config)
|
3
|
+
@args = args
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.name; raise NotImplementedError; end
|
8
|
+
def self.summary; raise NotImplementedError; end
|
9
|
+
def run; raise NotImplementedError; end
|
10
|
+
|
11
|
+
def self.inherited(subclass)
|
12
|
+
Snaptoken::Commands::LIST << subclass
|
13
|
+
end
|
14
|
+
|
15
|
+
ERROR_MSG = {
|
16
|
+
config: {
|
17
|
+
true: "You are not in a leg working directory.",
|
18
|
+
false: "You are already in a leg working directory."
|
19
|
+
},
|
20
|
+
config_title: {
|
21
|
+
true: "You need to set a title in leg.yml."
|
22
|
+
},
|
23
|
+
steps_folder: {
|
24
|
+
true: "There is no steps folder.",
|
25
|
+
false: "There is already a steps folder."
|
26
|
+
},
|
27
|
+
steps: {
|
28
|
+
true: "There are no steps in the steps folder."
|
29
|
+
},
|
30
|
+
repo: {
|
31
|
+
true: "There is no repo folder.",
|
32
|
+
false: "There is already a repo folder."
|
33
|
+
},
|
34
|
+
diff: {
|
35
|
+
true: "There is no steps.diff file."
|
36
|
+
},
|
37
|
+
doc: {
|
38
|
+
true: "There is no doc folder."
|
39
|
+
},
|
40
|
+
doc_out: {
|
41
|
+
true: "There are no doc output files."
|
42
|
+
},
|
43
|
+
ftp: {
|
44
|
+
true: "There is no ftp.yml file."
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
def needs!(*whats)
|
49
|
+
options = whats.pop if whats.last.is_a? Hash
|
50
|
+
options ||= {}
|
51
|
+
|
52
|
+
yes = Array(whats).flatten.map { |w| [w, true] }
|
53
|
+
no = Array(options[:not]).map { |w| [w, false] }
|
54
|
+
|
55
|
+
(yes + no).each do |what, v|
|
56
|
+
valid = false
|
57
|
+
case what
|
58
|
+
when :config
|
59
|
+
valid = true if @config
|
60
|
+
when :config_title
|
61
|
+
valid = true if @config[:title]
|
62
|
+
when :steps_folder
|
63
|
+
valid = true if File.exist?(File.join(@config[:path], "steps"))
|
64
|
+
when :steps
|
65
|
+
valid = true if steps.length > 0
|
66
|
+
when :repo
|
67
|
+
valid = true if File.exist?(File.join(@config[:path], "repo"))
|
68
|
+
when :diff
|
69
|
+
valid = true if File.exist?(File.join(@config[:path], "steps.diff"))
|
70
|
+
when :doc
|
71
|
+
valid = true if File.exist?(File.join(@config[:path], "doc"))
|
72
|
+
when :doc_out
|
73
|
+
valid = true if File.exist?(File.join(@config[:path], "doc/html_out"))
|
74
|
+
when :ftp
|
75
|
+
valid = true if File.exist?(File.join(@config[:path], "ftp.yml"))
|
76
|
+
else
|
77
|
+
raise NotImplementedError
|
78
|
+
end
|
79
|
+
|
80
|
+
if valid != v
|
81
|
+
puts "Error: " + ERROR_MSG[what][v.to_s.to_sym]
|
82
|
+
exit!
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def steps
|
88
|
+
@steps ||= Dir[File.join(@config[:path], "steps/*")].map do |f|
|
89
|
+
name = File.basename(f)
|
90
|
+
name if File.directory?(f) && name =~ /\A\d+(\.\d+)*(-\w+)*\z/
|
91
|
+
end.compact.sort_by { |s| s.split(".").map(&:to_i) }.reject { |s| s.to_i.zero? }
|
92
|
+
end
|
93
|
+
|
94
|
+
def current_step
|
95
|
+
if @config[:step_path]
|
96
|
+
File.basename(@config[:step_path])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def latest_step
|
101
|
+
steps.last
|
102
|
+
end
|
103
|
+
|
104
|
+
def current_or_latest_step
|
105
|
+
current_step || latest_step
|
106
|
+
end
|
107
|
+
|
108
|
+
def step_name(step)
|
109
|
+
parts = step.split('-')
|
110
|
+
if parts.length > 1
|
111
|
+
parts[1..-1].join('-')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def step_path(step)
|
116
|
+
File.join(@config[:path], "steps", step)
|
117
|
+
end
|
118
|
+
|
119
|
+
def select_step(step, &block)
|
120
|
+
puts "Selecting step: #{step}"
|
121
|
+
FileUtils.cd(step_path(step), &block)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Snaptoken::Commands::Deploy < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"deploy"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Pushes output files in doc/ to production server"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :doc, :doc_out, :ftp
|
12
|
+
|
13
|
+
only = @args.empty? ? nil : @args
|
14
|
+
|
15
|
+
FileUtils.cd(File.join(@config[:path], "doc/html_out")) do
|
16
|
+
ftp_config = YAML.load(File.read(File.join(@config[:path], "ftp.yml")))
|
17
|
+
Net::FTP.open(ftp_config[:host], ftp_config[:username], ftp_config[:password]) do |ftp|
|
18
|
+
ftp.chdir(ftp_config[:root])
|
19
|
+
Dir["**/*"].each do |f|
|
20
|
+
if only.nil? || only.any? { |o| f[o] }
|
21
|
+
puts f
|
22
|
+
if File.directory?(f)
|
23
|
+
ftp.mkdir(f) rescue Net::FTPPermError
|
24
|
+
elsif File.file?(f)
|
25
|
+
ftp.putbinaryfile(f)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Snaptoken::Commands::Diff < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"diff"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Convert repo into a single file containing diffs for each step"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :repo
|
12
|
+
|
13
|
+
FileUtils.cd(File.join(@config[:path], "repo")) do
|
14
|
+
patches = `git format-patch --stdout -p --no-signature --histogram --root master`
|
15
|
+
File.open("../steps.diff", "w") do |f|
|
16
|
+
step_num = 1
|
17
|
+
patches.each_line do |line|
|
18
|
+
if line =~ /^(From|Date|index)/
|
19
|
+
# skip
|
20
|
+
elsif line =~ /^Subject: \[[^\]]*\](.*)$/
|
21
|
+
f << "\n" unless step_num == 1
|
22
|
+
step_name = $1.strip
|
23
|
+
if step_name =~ /^\w+(-\w+)*$/
|
24
|
+
f << "~~~ step: #{step_name}\n"
|
25
|
+
else
|
26
|
+
f << "~~~ step\n"
|
27
|
+
end
|
28
|
+
step_num += 1
|
29
|
+
elsif line.chomp.length > 0
|
30
|
+
f << line
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,128 @@
|
|
1
|
+
class Snaptoken::Commands::Doc < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"doc"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Renders files in doc folder into an HTML book"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :config_title, :steps_folder, :steps, :doc
|
12
|
+
|
13
|
+
FileUtils.cd(File.join(@config[:path], "doc")) do
|
14
|
+
FileUtils.rm_rf("html_out")
|
15
|
+
FileUtils.mkdir("html_out")
|
16
|
+
|
17
|
+
copy_static_files
|
18
|
+
write_css
|
19
|
+
write_html_files(prerender_diffs)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class HTMLLineByLine < Rouge::Formatter
|
24
|
+
def initialize(formatter)
|
25
|
+
@formatter = formatter
|
26
|
+
end
|
27
|
+
|
28
|
+
def stream(tokens, &b)
|
29
|
+
token_lines(tokens) do |line|
|
30
|
+
line.each do |tok, val|
|
31
|
+
yield @formatter.span(tok, val)
|
32
|
+
end
|
33
|
+
yield "\n"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def copy_static_files
|
41
|
+
Dir["html_in/*"].each do |f|
|
42
|
+
name = File.basename(f)
|
43
|
+
unless %w(template.html style.css).include? name
|
44
|
+
FileUtils.cp_r(f, "html_out/#{name}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def write_css
|
50
|
+
@config[:rouge_theme] ||= "github"
|
51
|
+
if @config[:rouge_theme].is_a? String
|
52
|
+
theme = Rouge::Theme.find(@config[:rouge_theme])
|
53
|
+
elsif @config[:rouge_theme].is_a? Hash
|
54
|
+
theme = Class.new(Rouge::Themes::Base16)
|
55
|
+
theme.name "base16.custom"
|
56
|
+
theme.palette @config[:rouge_theme]
|
57
|
+
end
|
58
|
+
|
59
|
+
css = File.read("html_in/style.css")
|
60
|
+
css << theme.render(scope: ".highlight")
|
61
|
+
|
62
|
+
File.write("html_out/style.css", css)
|
63
|
+
end
|
64
|
+
|
65
|
+
def prerender_diffs
|
66
|
+
diffs = {}
|
67
|
+
FileUtils.cd("../steps") do
|
68
|
+
FileUtils.mkdir_p("0")
|
69
|
+
last_step = "0"
|
70
|
+
Dir["*"].sort_by(&:to_i).each do |step|
|
71
|
+
names = [step.to_i.to_s]
|
72
|
+
if step =~ /\d+\-([\w-]+)$/
|
73
|
+
names << $1
|
74
|
+
end
|
75
|
+
|
76
|
+
diff = Snaptoken::Diff.new(last_step, step)
|
77
|
+
|
78
|
+
names.each do |name|
|
79
|
+
diffs[name] = diff.html.values.join("\n")
|
80
|
+
end
|
81
|
+
|
82
|
+
last_step = step
|
83
|
+
end
|
84
|
+
FileUtils.rmdir("0")
|
85
|
+
end
|
86
|
+
diffs
|
87
|
+
end
|
88
|
+
|
89
|
+
def write_html_files(diffs)
|
90
|
+
html_template = File.read("html_in/template.html")
|
91
|
+
|
92
|
+
index = ""
|
93
|
+
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML)
|
94
|
+
Dir["*.md"].sort.each do |md_file|
|
95
|
+
html_file = md_file.sub(/\.md$/, '.html')
|
96
|
+
|
97
|
+
md = File.read(md_file)
|
98
|
+
md =~ /^# (.+)$/
|
99
|
+
title = $1
|
100
|
+
|
101
|
+
index << "<li><a href='#{html_file}'>#{title}</a></li>\n"
|
102
|
+
|
103
|
+
content = markdown.render(md)
|
104
|
+
content.gsub!(/<p>{{([\w-]+)}}<\/p>/) { diffs[$1] }
|
105
|
+
|
106
|
+
html = html_template.dup
|
107
|
+
html.gsub!("{{title}}") { "#{@config[:title]} | #{title}" }
|
108
|
+
html.gsub!("{{content}}") { content }
|
109
|
+
|
110
|
+
File.write(File.join("html_out", html_file), html)
|
111
|
+
end
|
112
|
+
|
113
|
+
content = <<~HTML
|
114
|
+
<h1>#{@config[:title]}</h1>
|
115
|
+
<h2>Table of Contents</h2>
|
116
|
+
<ol>
|
117
|
+
#{index}
|
118
|
+
</ol>
|
119
|
+
HTML
|
120
|
+
|
121
|
+
html = html_template.dup
|
122
|
+
html.gsub!("{{title}}", @config[:title])
|
123
|
+
html.gsub!("{{content}}", content)
|
124
|
+
|
125
|
+
File.write("html_out/index.html", html)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Snaptoken::Commands::Fancy < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"fancy"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Run steps.diff through colordiff, diff-so-fancy, and less"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :diff
|
12
|
+
|
13
|
+
FileUtils.cd(@config[:path]) do
|
14
|
+
exec("cat steps.diff | colordiff | diff-so-fancy | less --tabs=4 -RFX")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def apply_diff(dir, diff)
|
21
|
+
stdin = IO.popen("git --git-dir= apply --directory=#{dir} -", "w")
|
22
|
+
stdin.write diff
|
23
|
+
stdin.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Snaptoken::Commands::Help < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"help"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Print out this help"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
puts "Usage: leg <command> [args...]"
|
12
|
+
puts
|
13
|
+
puts "Commands:"
|
14
|
+
Snaptoken::Commands::LIST.each do |cmd|
|
15
|
+
puts " #{cmd.name}"
|
16
|
+
puts " #{cmd.summary}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Snaptoken::Commands::Pieces < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"pieces"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Print an inventory of the number of tokens used between two steps"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
puts "Not implemented"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Snaptoken::Commands::Ref < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"ref"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Convert a step number or name to a git commit reference"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :repo
|
12
|
+
|
13
|
+
ref = @args.first
|
14
|
+
is_num = (ref =~ /\A\d+\z/)
|
15
|
+
|
16
|
+
FileUtils.cd(@config[:path]) do
|
17
|
+
repo = Rugged::Repository.new("repo")
|
18
|
+
|
19
|
+
walker = Rugged::Walker.new(repo)
|
20
|
+
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
21
|
+
walker.push(repo.branches.find { |b| b.name == "master" }.target)
|
22
|
+
walker.each.with_index do |commit, idx|
|
23
|
+
step_num = (idx + 1).to_s
|
24
|
+
step_name = commit.message.lines.first.strip
|
25
|
+
|
26
|
+
if (is_num && ref == step_num) || (!is_num && ref == step_name)
|
27
|
+
puts commit.oid
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
puts "Error: reference not found"
|
33
|
+
exit!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
class Snaptoken::Commands::Repo < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"repo"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Convert steps folder into a version controlled repository"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :steps_folder, :steps, not: :repo
|
12
|
+
|
13
|
+
FileUtils.cd(@config[:path])
|
14
|
+
|
15
|
+
FileUtils.mkdir("repo")
|
16
|
+
repo = Rugged::Repository.init_at("repo")
|
17
|
+
|
18
|
+
steps.each do |step|
|
19
|
+
index = repo.index
|
20
|
+
index.read_tree(repo.head.target.tree) unless repo.empty?
|
21
|
+
|
22
|
+
FileUtils.cd(step_path(step)) do
|
23
|
+
Dir["**/*"].each do |path|
|
24
|
+
unless File.directory?(path)
|
25
|
+
oid = repo.write(File.read(path), :blob)
|
26
|
+
index.add(path: path, oid: oid, mode: 0100644)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
options = {}
|
32
|
+
options[:tree] = index.write_tree(repo)
|
33
|
+
options[:message] = step_name(step) || "-"
|
34
|
+
options[:parents] = repo.empty? ? [] : [repo.head.target]
|
35
|
+
options[:update_ref] = 'HEAD'
|
36
|
+
|
37
|
+
Rugged::Commit.create(repo, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
repo.checkout_head(strategy: :force)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Snaptoken::Commands::Undiff < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"undiff"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Convert steps.diff to steps folder"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :diff, not: :steps_folder
|
12
|
+
|
13
|
+
FileUtils.cd(@config[:path]) do
|
14
|
+
FileUtils.mkdir("steps")
|
15
|
+
FileUtils.cd("steps") do
|
16
|
+
File.open("../steps.diff", "r") do |f|
|
17
|
+
step_num = 0
|
18
|
+
step_dir = nil
|
19
|
+
prev_dir = nil
|
20
|
+
cur_diff = nil
|
21
|
+
while line = f.gets
|
22
|
+
if line =~ /^~~~ step(: \w+(-\w+)*)?$/
|
23
|
+
if cur_diff
|
24
|
+
apply_diff(step_dir, cur_diff)
|
25
|
+
cur_diff = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
step_num += 1
|
29
|
+
step_dir = step_num.to_s
|
30
|
+
step_dir += "-#{$1[2..-1]}" if $1
|
31
|
+
if step_num == 1
|
32
|
+
FileUtils.mkdir(step_dir)
|
33
|
+
else
|
34
|
+
FileUtils.cp_r(prev_dir, step_dir)
|
35
|
+
end
|
36
|
+
prev_dir = step_dir
|
37
|
+
elsif line =~ /^diff --git/
|
38
|
+
apply_diff(step_dir, cur_diff) if cur_diff
|
39
|
+
cur_diff = line
|
40
|
+
elsif cur_diff
|
41
|
+
cur_diff << line
|
42
|
+
end
|
43
|
+
end
|
44
|
+
apply_diff(step_dir, cur_diff) if cur_diff
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def apply_diff(dir, diff)
|
53
|
+
stdin = IO.popen("git --git-dir= apply --directory=#{dir} -", "w")
|
54
|
+
stdin.write diff
|
55
|
+
stdin.close
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Snaptoken::Commands::Unrepo < Snaptoken::Commands::BaseCommand
|
2
|
+
def self.name
|
3
|
+
"unrepo"
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.summary
|
7
|
+
"Convert repository into steps folder"
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
needs! :config, :repo, not: :steps_folder
|
12
|
+
|
13
|
+
FileUtils.cd(@config[:path]) do
|
14
|
+
FileUtils.mkdir("steps")
|
15
|
+
|
16
|
+
repo = Rugged::Repository.new("repo")
|
17
|
+
|
18
|
+
walker = Rugged::Walker.new(repo)
|
19
|
+
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
20
|
+
walker.push(repo.branches.find { |b| b.name == "master" }.target)
|
21
|
+
walker.each.with_index do |commit, idx|
|
22
|
+
step_num = (idx + 1).to_s
|
23
|
+
step_name = commit.message.lines.first.strip
|
24
|
+
|
25
|
+
if step_name.empty?
|
26
|
+
step = step_num
|
27
|
+
else
|
28
|
+
step = "#{step_num}-#{step_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
repo.checkout(commit.oid, strategy: :force,
|
32
|
+
target_directory: step_path(step))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Snaptoken::Commands
|
2
|
+
LIST = []
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'snaptoken/commands/base_command'
|
6
|
+
|
7
|
+
require 'snaptoken/commands/deploy'
|
8
|
+
require 'snaptoken/commands/diff'
|
9
|
+
require 'snaptoken/commands/doc'
|
10
|
+
require 'snaptoken/commands/help'
|
11
|
+
require 'snaptoken/commands/pieces'
|
12
|
+
require 'snaptoken/commands/fancy'
|
13
|
+
require 'snaptoken/commands/ref'
|
14
|
+
require 'snaptoken/commands/repo'
|
15
|
+
require 'snaptoken/commands/undiff'
|
16
|
+
require 'snaptoken/commands/unrepo'
|
17
|
+
|
@@ -0,0 +1,201 @@
|
|
1
|
+
class Snaptoken::Diff
|
2
|
+
GIT_DIFF_OPTIONS = "--histogram --unified=100000 --ignore-space-change --no-index"
|
3
|
+
|
4
|
+
attr_reader :files, :html
|
5
|
+
|
6
|
+
def initialize(step_a, step_b)
|
7
|
+
git_diff = `git diff #{GIT_DIFF_OPTIONS} #{step_a} #{step_b}`
|
8
|
+
parse_git_diff(git_diff)
|
9
|
+
@files.values.each(&:omit_adjacent_removals!)
|
10
|
+
|
11
|
+
@html = {}
|
12
|
+
@files.each do |filename, file|
|
13
|
+
@html[filename] = file.to_html
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class DiffLine
|
18
|
+
attr_reader :type, :line
|
19
|
+
attr_writer :type
|
20
|
+
|
21
|
+
def initialize(type, line)
|
22
|
+
@type = type
|
23
|
+
@line = line
|
24
|
+
end
|
25
|
+
|
26
|
+
def empty!; @empty = true; end
|
27
|
+
def empty?; @empty; end
|
28
|
+
|
29
|
+
def omit!; @omit = true; end
|
30
|
+
def omit?; @omit; end
|
31
|
+
end
|
32
|
+
|
33
|
+
class DiffSection
|
34
|
+
attr_reader :type, :lines, :contents
|
35
|
+
|
36
|
+
def initialize(type, line=nil)
|
37
|
+
@type = type
|
38
|
+
@lines = Array(line)
|
39
|
+
@contents = []
|
40
|
+
end
|
41
|
+
|
42
|
+
def <<(content)
|
43
|
+
@contents << content
|
44
|
+
end
|
45
|
+
|
46
|
+
def dirty!; @dirty = true; end
|
47
|
+
def dirty?; @dirty; end
|
48
|
+
end
|
49
|
+
|
50
|
+
class DiffFile < DiffSection
|
51
|
+
attr_reader :filename, :file_contents
|
52
|
+
|
53
|
+
def initialize(filename)
|
54
|
+
super(:file)
|
55
|
+
@filename = filename
|
56
|
+
@file_contents = ""
|
57
|
+
end
|
58
|
+
|
59
|
+
def append_line(line)
|
60
|
+
@file_contents << line
|
61
|
+
@file_contents << "\n" unless line.end_with? "\n"
|
62
|
+
end
|
63
|
+
|
64
|
+
def new_file!; @new_file = true; end
|
65
|
+
def new_file?; @new_file; end
|
66
|
+
|
67
|
+
def omit_adjacent_removals!
|
68
|
+
change_chain = []
|
69
|
+
to_render = @contents.dup
|
70
|
+
until to_render.empty?
|
71
|
+
cur = to_render.shift
|
72
|
+
if cur.is_a? DiffSection
|
73
|
+
if cur.dirty?
|
74
|
+
to_render = cur.contents + to_render
|
75
|
+
else
|
76
|
+
[change_chain.first, change_chain.last].compact.each do |line|
|
77
|
+
line.type = :nochange if line.empty?
|
78
|
+
end
|
79
|
+
change_chain = []
|
80
|
+
end
|
81
|
+
else
|
82
|
+
if cur.type == :nochange
|
83
|
+
[change_chain.first, change_chain.last].compact.each do |line|
|
84
|
+
line.type = :nochange if line.empty?
|
85
|
+
end
|
86
|
+
change_chain = []
|
87
|
+
else
|
88
|
+
change_chain << cur
|
89
|
+
if cur.type == :add
|
90
|
+
change_chain.each { |c| c.omit! if c.type == :remove }
|
91
|
+
elsif cur.type == :remove
|
92
|
+
cur.omit! if change_chain.any? { |c| c.type == :add }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_html
|
100
|
+
formatter = Rouge::Formatters::HTML.new
|
101
|
+
formatter = HTMLLineByLine.new(formatter)
|
102
|
+
|
103
|
+
lexer = Rouge::Lexer.guess(filename: @filename)
|
104
|
+
code_hl = formatter.format(lexer.lex(@file_contents)).lines.each(&:chomp!)
|
105
|
+
|
106
|
+
html = ""
|
107
|
+
html << "<div class=\"diff\">\n"
|
108
|
+
html << "<div class=\"filename\">#{@filename}</div>\n"
|
109
|
+
html << "<pre class=\"highlight\"><code>"
|
110
|
+
|
111
|
+
to_render = @contents.dup
|
112
|
+
until to_render.empty?
|
113
|
+
cur = to_render.shift
|
114
|
+
if cur.is_a? DiffSection
|
115
|
+
if cur.dirty?
|
116
|
+
to_render = cur.contents + to_render
|
117
|
+
else
|
118
|
+
summary = cur.lines.map { |n| code_hl[n] }.join(" ... ").gsub("\n", "")
|
119
|
+
html << "<div class=\"line folded\">#{summary}</div>"
|
120
|
+
end
|
121
|
+
elsif !cur.omit?
|
122
|
+
tag = {nochange: :div, add: :ins, remove: :del}[cur.type]
|
123
|
+
tag = :div if new_file?
|
124
|
+
html << "<#{tag} class=\"line\">#{code_hl[cur.line]}</#{tag}>"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
html << "</code></pre>\n</div>\n"
|
128
|
+
|
129
|
+
html
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class HTMLLineByLine < Rouge::Formatter
|
134
|
+
def initialize(formatter)
|
135
|
+
@formatter = formatter
|
136
|
+
end
|
137
|
+
|
138
|
+
def stream(tokens, &b)
|
139
|
+
token_lines(tokens) do |line|
|
140
|
+
line.each do |tok, val|
|
141
|
+
yield @formatter.span(tok, val)
|
142
|
+
end
|
143
|
+
yield "\n"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
def parse_git_diff(git_diff)
|
151
|
+
diff_file = nil
|
152
|
+
section_stack = nil
|
153
|
+
line_idx = nil
|
154
|
+
in_diff = false
|
155
|
+
@files = {}
|
156
|
+
|
157
|
+
git_diff.lines.each do |line|
|
158
|
+
if !in_diff && line =~ /^diff --git (\S+) (\S+)$/
|
159
|
+
diff_file = DiffFile.new(File.basename($2))
|
160
|
+
@files[diff_file.filename] = diff_file
|
161
|
+
section_stack = [diff_file]
|
162
|
+
line_idx = -1
|
163
|
+
elsif !in_diff && line.start_with?('new file')
|
164
|
+
diff_file.new_file!
|
165
|
+
elsif line.start_with? '@@'
|
166
|
+
in_diff = true
|
167
|
+
elsif in_diff && [' ', '+', '-'].include?(line[0])
|
168
|
+
type = {' ' => :nochange, '+' => :add, '-' => :remove }[line[0]]
|
169
|
+
diff_file.append_line(line[1..-1])
|
170
|
+
line_idx += 1
|
171
|
+
|
172
|
+
section_stack.each(&:dirty!) if type != :nochange
|
173
|
+
|
174
|
+
if line[1..-1] =~ /^\/\*\*\* (.+) \*\*\*\/$/
|
175
|
+
section = DiffSection.new(:comment, line_idx)
|
176
|
+
diff_file << section
|
177
|
+
section_stack = [diff_file, section]
|
178
|
+
elsif line[1] =~ /\S/ && line.chomp[-1] == "{"
|
179
|
+
section = DiffSection.new(:braces, line_idx)
|
180
|
+
section_stack.pop if section_stack.last.type == :braces
|
181
|
+
section_stack.last << section
|
182
|
+
section_stack.push(section)
|
183
|
+
end
|
184
|
+
|
185
|
+
diff_line = DiffLine.new(type, line_idx)
|
186
|
+
diff_line.empty! if line[1..-1].strip.empty?
|
187
|
+
section_stack.last << diff_line
|
188
|
+
|
189
|
+
if line[1..-1] =~ /^}( \w+)?;?$/ && section_stack.last.type == :braces
|
190
|
+
section = section_stack.pop
|
191
|
+
section.lines << line_idx
|
192
|
+
end
|
193
|
+
|
194
|
+
section_stack.each(&:dirty!) if type != :nochange
|
195
|
+
else
|
196
|
+
in_diff = false
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
data/lib/snaptoken.rb
ADDED
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snaptoken
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.10.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Ruten
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rugged
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.25.1.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.25.1.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redcarpet
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 3.4.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 3.4.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rouge
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.0.7
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.0.7
|
55
|
+
description:
|
56
|
+
email: jeremy.ruten@gmail.com
|
57
|
+
executables:
|
58
|
+
- leg
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/leg
|
63
|
+
- lib/snaptoken.rb
|
64
|
+
- lib/snaptoken/cli.rb
|
65
|
+
- lib/snaptoken/commands.rb
|
66
|
+
- lib/snaptoken/commands/base_command.rb
|
67
|
+
- lib/snaptoken/commands/deploy.rb
|
68
|
+
- lib/snaptoken/commands/diff.rb
|
69
|
+
- lib/snaptoken/commands/doc.rb
|
70
|
+
- lib/snaptoken/commands/fancy.rb
|
71
|
+
- lib/snaptoken/commands/help.rb
|
72
|
+
- lib/snaptoken/commands/pieces.rb
|
73
|
+
- lib/snaptoken/commands/ref.rb
|
74
|
+
- lib/snaptoken/commands/repo.rb
|
75
|
+
- lib/snaptoken/commands/undiff.rb
|
76
|
+
- lib/snaptoken/commands/unrepo.rb
|
77
|
+
- lib/snaptoken/diff.rb
|
78
|
+
homepage: https://github.com/yjerem/leg
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.6.8
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: tools for .leg files
|
102
|
+
test_files: []
|