snaptoken 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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: []