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 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,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '../lib')
4
+
5
+ require 'snaptoken'
6
+
7
+ cli = Snaptoken::CLI.new
8
+ cli.run(ARGV)
9
+
@@ -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
@@ -0,0 +1,14 @@
1
+ require 'fileutils'
2
+ require 'net/ftp'
3
+ require 'yaml'
4
+ require 'rugged'
5
+ require 'redcarpet'
6
+ require 'rouge'
7
+
8
+ module Snaptoken
9
+ end
10
+
11
+ require 'snaptoken/cli'
12
+ require 'snaptoken/commands'
13
+ require 'snaptoken/diff'
14
+
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: []