pptxt 0.1.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/pptxt +151 -0
- data/lib/pptxt.rb +76 -0
- data/lib/pptxt_error.rb +16 -0
- data/lib/pptxt_exit_status.rb +9 -0
- data/lib/pptxt_slide.rb +176 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: af8adcaf1ae4e8b2e76aaadff580d8d0ff48534f
|
4
|
+
data.tar.gz: 3324a84c53ff4ae4865a92530d62875c0a662492
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fabd38653a74ae682deb4e74e081cca977a50f3cb4186c5b3daba60e98f4f2ffda3e976ec54a003742732fa7f85c8c7a1060d0f06ed56a56b86c472933688469
|
7
|
+
data.tar.gz: 974f4152ce33df205b76afaa4fd669fb052de111e6ee6fe6f059dc1d01bcb5c025b77a4d3b95401b3cff7088c2cf83b1e081e2e12bffafecebd8f7bb455a6e7b
|
data/bin/pptxt
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "io/wait"
|
4
|
+
require "optparse"
|
5
|
+
require "pathname"
|
6
|
+
require "pptxt"
|
7
|
+
require "pptxt_exit_status"
|
8
|
+
require "pptxt_slide"
|
9
|
+
|
10
|
+
def parse(args)
|
11
|
+
options = Hash.new
|
12
|
+
options["detailed"] = false
|
13
|
+
options["git"] = false
|
14
|
+
options["pptx"] = nil
|
15
|
+
options["slideshow"] = false
|
16
|
+
|
17
|
+
parser = OptionParser.new do |opts|
|
18
|
+
opts.banner = "Usage: #{File.basename($0)} [OPTIONS] [pptx]"
|
19
|
+
|
20
|
+
opts.on(
|
21
|
+
"-c",
|
22
|
+
"--configure",
|
23
|
+
"Configure git repo to use pptxt"
|
24
|
+
) do
|
25
|
+
PPtxt.configure_git
|
26
|
+
exit PPtxtExitStatus::GOOD
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("-d", "--detailed", "Display full xml") do
|
30
|
+
options["detailed"] = true
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("--git", "Hide the slide dividers for git-diff") do
|
34
|
+
options["git"] = true
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on(
|
38
|
+
"-g",
|
39
|
+
"--global-config",
|
40
|
+
"Configure git to use pptxt globally"
|
41
|
+
) do
|
42
|
+
PPtxt.configure_git(true)
|
43
|
+
exit PPtxtExitStatus::GOOD
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-h", "--help", "Display this help message") do
|
47
|
+
puts opts
|
48
|
+
exit PPtxtExitStatus::GOOD
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-s", "--slideshow", "Display as slideshow") do
|
52
|
+
options["slideshow"] = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
begin
|
57
|
+
parser.parse!
|
58
|
+
rescue OptionParser::InvalidOption => e
|
59
|
+
puts e.message
|
60
|
+
puts parser
|
61
|
+
exit PPtxtExitStatus::INVALID_OPTION
|
62
|
+
rescue OptionParser::InvalidArgument => e
|
63
|
+
puts e.message
|
64
|
+
puts parser
|
65
|
+
exit PPtxtExitStatus::INVALID_ARGUMENT
|
66
|
+
rescue OptionParser::MissingArgument => e
|
67
|
+
puts e.message
|
68
|
+
puts parser
|
69
|
+
exit PPtxtExitStatus::MISSING_ARGUMENT
|
70
|
+
end
|
71
|
+
|
72
|
+
if (args.empty?)
|
73
|
+
puts parser
|
74
|
+
exit PPtxtExitStatus::MISSING_ARGUMENT
|
75
|
+
elsif (args.length > 1)
|
76
|
+
puts parser
|
77
|
+
exit PPtxtExitStatus::EXTRA_ARGUMENTS
|
78
|
+
end
|
79
|
+
|
80
|
+
if (!Pathname.new(args[0]).expand_path.exist?)
|
81
|
+
puts "#{args[0]} does not exist!"
|
82
|
+
exit PPtxtExitStatus::FILE_DOES_NOT_EXIST
|
83
|
+
end
|
84
|
+
|
85
|
+
options["pptx"] = args[0].strip
|
86
|
+
if (options["pptx"] == "/dev/null")
|
87
|
+
exit PPtxtExitStatus::GOOD
|
88
|
+
end
|
89
|
+
|
90
|
+
return options
|
91
|
+
end
|
92
|
+
|
93
|
+
# Parse CLI args
|
94
|
+
options = parse(ARGV)
|
95
|
+
|
96
|
+
# Get slides
|
97
|
+
slides = PPtxt.new(options["pptx"]).slides
|
98
|
+
|
99
|
+
if (!options["slideshow"])
|
100
|
+
# Loop through slides
|
101
|
+
slides.each do |slide|
|
102
|
+
if (options["detailed"])
|
103
|
+
puts slide.detailed
|
104
|
+
elsif (options["git"])
|
105
|
+
puts slide.diffable
|
106
|
+
puts
|
107
|
+
else
|
108
|
+
puts slide
|
109
|
+
puts
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
quit = false
|
114
|
+
count = 0
|
115
|
+
|
116
|
+
while (!quit)
|
117
|
+
slide = slides[count]
|
118
|
+
|
119
|
+
# Make it human readable and parse
|
120
|
+
system("clear")
|
121
|
+
puts
|
122
|
+
puts slide
|
123
|
+
puts
|
124
|
+
puts "j:Next k:Previous q:Quit"
|
125
|
+
|
126
|
+
answer = nil
|
127
|
+
while (!answer)
|
128
|
+
begin
|
129
|
+
system("stty raw -echo")
|
130
|
+
if $stdin.ready?
|
131
|
+
answer = $stdin.getc.chr
|
132
|
+
else
|
133
|
+
sleep 0.1
|
134
|
+
end
|
135
|
+
ensure
|
136
|
+
system("stty -raw echo")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
puts
|
140
|
+
|
141
|
+
case answer
|
142
|
+
when "j", "J"
|
143
|
+
count += 1 if (count < (slides.length - 1))
|
144
|
+
when "k", "K"
|
145
|
+
count -= 1 if (count > 0)
|
146
|
+
when "q", "Q", "\x03"
|
147
|
+
# Quit or ^C
|
148
|
+
quit = true
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/pptxt.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require "pptxt_error"
|
2
|
+
require "pptxt_slide"
|
3
|
+
require "scoobydoo"
|
4
|
+
|
5
|
+
class PPtxt
|
6
|
+
attr_accessor :slides
|
7
|
+
|
8
|
+
def self.configure_git(global = false)
|
9
|
+
if (ScoobyDoo.where_are_you("git").nil?)
|
10
|
+
raise PPtxtError::MissingDependency.new("git")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Configure git
|
14
|
+
flag = ""
|
15
|
+
flag = "--global" if (global)
|
16
|
+
system(
|
17
|
+
"git config #{flag} diff.pptxt.textconv \"pptxt --git\""
|
18
|
+
)
|
19
|
+
|
20
|
+
# Setup .gitattributes
|
21
|
+
filename = ".gitattributes"
|
22
|
+
if (global)
|
23
|
+
cfg = "git config --global core.attributesfile"
|
24
|
+
filename = %x(#{cfg}).strip
|
25
|
+
if (filename.nil? || filename.empty?)
|
26
|
+
filename = "~/.gitattributes"
|
27
|
+
system("#{cfg} \"#{filename}\"")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
new_line = "*.pptx diff=pptxt\n"
|
31
|
+
|
32
|
+
file = Pathname.new(filename).expand_path
|
33
|
+
if (file.exist?)
|
34
|
+
File.open(file) do |f|
|
35
|
+
f.each_line do |line|
|
36
|
+
if (line == new_line)
|
37
|
+
return
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
File.open(file, "a") do |f|
|
42
|
+
f.write(new_line)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
File.open(file, "w") do |f|
|
46
|
+
f.write(new_line)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_slides
|
52
|
+
slides = %x(
|
53
|
+
unzip -l "#{@pptx}" | \grep -E "ppt/slides/[^_]" |
|
54
|
+
awk '{print $4}' | sort -k 1.17n
|
55
|
+
).split("\n")
|
56
|
+
|
57
|
+
count = 0
|
58
|
+
slides.each do |slide|
|
59
|
+
xml = %x(unzip -qc "#{@pptx}" #{slide}).gsub("<", "\n<")
|
60
|
+
count += 1
|
61
|
+
@slides.push(PPtxtSlide.new(xml, count))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
private :create_slides
|
65
|
+
|
66
|
+
def initialize(pptx)
|
67
|
+
@pptx = pptx
|
68
|
+
@slides = Array.new
|
69
|
+
|
70
|
+
if (ScoobyDoo.where_are_you("unzip").nil?)
|
71
|
+
raise PPtxtError::MissingDependency.new("unzip")
|
72
|
+
end
|
73
|
+
|
74
|
+
create_slides
|
75
|
+
end
|
76
|
+
end
|
data/lib/pptxt_error.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
module PPtxtError
|
2
|
+
class Error < RuntimeError
|
3
|
+
end
|
4
|
+
|
5
|
+
class MissingDependency < Error
|
6
|
+
def initialize(tool)
|
7
|
+
super("Missing dependency: #{tool}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class UnknownXML < Error
|
12
|
+
def initialize(line)
|
13
|
+
super("Unknown line in xml: #{line}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/pptxt_slide.rb
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
class PPtxtSlide
|
2
|
+
def detailed
|
3
|
+
ret = Array.new
|
4
|
+
num_indents = 0
|
5
|
+
|
6
|
+
@xml.each_line do |line|
|
7
|
+
line.strip!
|
8
|
+
indents = Array.new(num_indents, " ").join
|
9
|
+
|
10
|
+
case line
|
11
|
+
when %r{^<\?.+$}, ""
|
12
|
+
# Ignore xml version and blank lines
|
13
|
+
when %r{^<.+/> *$}, %r{^<[^/].+>.*</.+> *$}
|
14
|
+
# Don't indent if one-liner
|
15
|
+
ret.push("#{indents}#{line}")
|
16
|
+
when %r{^<[^/].+$}
|
17
|
+
# Indent after opening tag
|
18
|
+
ret.push("#{indents}#{line}")
|
19
|
+
num_indents += 1
|
20
|
+
when %r{^</.+> *$}
|
21
|
+
# Remove indent after closing tag
|
22
|
+
num_indents -= 1
|
23
|
+
indents = Array.new(num_indents, " ").join
|
24
|
+
ret.push("#{indents}#{line}")
|
25
|
+
else
|
26
|
+
raise PPtxtError::UnknownXML.new(line)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
return ret.join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def diffable
|
34
|
+
out = Array.new
|
35
|
+
out.push(@title) if (!@title.empty?)
|
36
|
+
out.push(@subtitle) if (!@subtitle.empty?)
|
37
|
+
out.push("\n") if (!@title.empty? || !@subtitle.empty?)
|
38
|
+
out.push(@content) if (!@content.empty?)
|
39
|
+
return out.join.strip
|
40
|
+
end
|
41
|
+
|
42
|
+
def handle_format(str, format, lvl, list_index)
|
43
|
+
case format
|
44
|
+
when "bullet"
|
45
|
+
return "#{Array.new(lvl, " ").join}- #{str}"
|
46
|
+
when "number"
|
47
|
+
return "#{Array.new(lvl, " ").join}#{list_index}. #{str}"
|
48
|
+
else
|
49
|
+
return str
|
50
|
+
end
|
51
|
+
end
|
52
|
+
private :handle_format
|
53
|
+
|
54
|
+
def initialize(xml, count)
|
55
|
+
@content = ""
|
56
|
+
@count = count
|
57
|
+
@subtitle = ""
|
58
|
+
@title = ""
|
59
|
+
@xml = xml
|
60
|
+
parse_xml
|
61
|
+
end
|
62
|
+
|
63
|
+
def parse_xml
|
64
|
+
can_be_newline = false
|
65
|
+
first_time = false
|
66
|
+
ignore = false
|
67
|
+
in_subtitle = false
|
68
|
+
in_title = false
|
69
|
+
was_newline = true
|
70
|
+
|
71
|
+
format = "bullet"
|
72
|
+
lvl = 0
|
73
|
+
numlist_count = Array.new
|
74
|
+
|
75
|
+
@xml.each_line do |line|
|
76
|
+
line.strip!
|
77
|
+
case line
|
78
|
+
when "<a:p>"
|
79
|
+
# Assume bullet list
|
80
|
+
format = "bullet"
|
81
|
+
when %r{<a:pPr.*lvl="[^"]+".*}
|
82
|
+
# Sub bullet/item
|
83
|
+
lvl = line[%r{<a:pPr.*lvl="([^"]+)".*}, 1].to_i
|
84
|
+
when %r{<.?a:buNone}
|
85
|
+
# Regular text
|
86
|
+
format = "text"
|
87
|
+
when %r{<.?a:buAutoNum}
|
88
|
+
# Numbered list
|
89
|
+
format = "number"
|
90
|
+
when %r{<p:cNvPr .*Title}
|
91
|
+
# Setup titles
|
92
|
+
in_title = true
|
93
|
+
first_time = true
|
94
|
+
@title += "# "
|
95
|
+
when %r{<p:cNvPr .*Subtitle}
|
96
|
+
# Setup subtitles
|
97
|
+
in_subtitle = true
|
98
|
+
first_time = true
|
99
|
+
@subtitle += "## "
|
100
|
+
when %r{<a:t>.*}
|
101
|
+
# Handle text
|
102
|
+
if (in_title)
|
103
|
+
@title += " " if (!first_time && was_newline)
|
104
|
+
@title += line[5..-1]
|
105
|
+
elsif (in_subtitle)
|
106
|
+
@subtitle += " " if (!first_time && was_newline)
|
107
|
+
@subtitle += line[5..-1]
|
108
|
+
elsif(ignore)
|
109
|
+
break
|
110
|
+
else
|
111
|
+
if (was_newline)
|
112
|
+
list_index = numlist_count[lvl] || 1
|
113
|
+
@content += handle_format(
|
114
|
+
line[5..-1],
|
115
|
+
format,
|
116
|
+
lvl,
|
117
|
+
list_index
|
118
|
+
)
|
119
|
+
if (format == "number")
|
120
|
+
numlist_count[lvl] = list_index + 1
|
121
|
+
end
|
122
|
+
else
|
123
|
+
@content += line[5..-1]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
first_time = false
|
128
|
+
was_newline = false
|
129
|
+
lvl = 0
|
130
|
+
when "</a:t>"
|
131
|
+
# Setup newlines
|
132
|
+
can_be_newline = true
|
133
|
+
when "<a:br>", "</a:p>", "</p:txBody>"
|
134
|
+
# Handle newlines
|
135
|
+
if (in_title)
|
136
|
+
@title += "\n" if (can_be_newline)
|
137
|
+
elsif (in_subtitle)
|
138
|
+
@subtitle += "\n" if (can_be_newline)
|
139
|
+
elsif(ignore)
|
140
|
+
break
|
141
|
+
else
|
142
|
+
@content += "\n" if (can_be_newline)
|
143
|
+
end
|
144
|
+
|
145
|
+
can_be_newline = false
|
146
|
+
was_newline = true
|
147
|
+
|
148
|
+
if (line == "</p:txBody>")
|
149
|
+
in_title = false
|
150
|
+
in_subtitle = false
|
151
|
+
end
|
152
|
+
when "<p:graphicFrame>"
|
153
|
+
# Ignore graphics for now
|
154
|
+
# FIXME
|
155
|
+
ignore = true
|
156
|
+
when "</p:graphicFrame>"
|
157
|
+
ignore = false
|
158
|
+
else
|
159
|
+
# Ignore
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
private :parse_xml
|
164
|
+
|
165
|
+
def to_s()
|
166
|
+
div = Array.new(70, "-").join
|
167
|
+
out = Array.new
|
168
|
+
out.push("#{div}\n")
|
169
|
+
out.push(@title) if (!@title.empty?)
|
170
|
+
out.push(@subtitle) if (!@subtitle.empty?)
|
171
|
+
out.push("\n") if (!@title.empty? || !@subtitle.empty?)
|
172
|
+
out.push(@content) if (!@content.empty?)
|
173
|
+
out.push("#{div} #{@count}\n")
|
174
|
+
return out.join.strip
|
175
|
+
end
|
176
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pptxt
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Miles Whittaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.8'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 5.8.1
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '5.8'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 5.8.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: scoobydoo
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.1'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 0.1.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0.1'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 0.1.0
|
53
|
+
description: This gem can extract the xml info from a pptx and convert to human-readable
|
54
|
+
text. It was intended to be used with git for seeing changes between revisions.
|
55
|
+
email: mjwhitta@gmail.com
|
56
|
+
executables:
|
57
|
+
- pptxt
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- bin/pptxt
|
62
|
+
- lib/pptxt.rb
|
63
|
+
- lib/pptxt_error.rb
|
64
|
+
- lib/pptxt_exit_status.rb
|
65
|
+
- lib/pptxt_slide.rb
|
66
|
+
homepage: http://mjwhitta.github.io/pptxt
|
67
|
+
licenses:
|
68
|
+
- GPL-3.0
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 2.4.8
|
87
|
+
signing_key:
|
88
|
+
specification_version: 4
|
89
|
+
summary: Converts pptx files to human-readable text
|
90
|
+
test_files: []
|
91
|
+
has_rdoc:
|