cheap-impression-presenter 1.0.0 → 1.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 +4 -4
- data/INSTALL.md +6 -2
- data/README.md +78 -14
- data/chimp.gemspec +2 -1
- data/lib/ChimpParser-Grammar.rb +15 -6
- data/lib/ChimpParser.rb +30 -20
- data/lib/ChimpPlugin.rb +2 -0
- data/lib/output/pdf.rb +226 -0
- data/lib/output/screen.rb +41 -17
- data/lib/plugins/sixel.rb +44 -13
- data/tools/chimp +8 -4
- metadata +19 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8154b5025a9eb30f03a8b34d198e3e9024a0540ae450fb1ed4af0194982fbebf
|
|
4
|
+
data.tar.gz: e732455c9d6666b9d1bc672aa620f2a029cdc3d34d425478185ddfe2160222aa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d25d85a0c3c3f63f0e46af0c2234868882a1f568efd6a835595f7c682b3e279deb85ec6c2261586e9bad967daac4409ada813d1a7c6db8ff67fd323f3af376cb
|
|
7
|
+
data.tar.gz: cc337df9d9f494027e1e0179890c606bd4d0133ec60e0e7b5de948e0a77072c56503c690421782345fd2060ecb809a14ff6c274c3219e689de9db1855469b6aa
|
data/INSTALL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
```bash
|
|
2
|
-
gem install
|
|
2
|
+
gem install cheap-impression-presenter
|
|
3
3
|
```
|
|
4
4
|
|
|
5
5
|
It wont work in a windows terminal for now.
|
|
@@ -10,4 +10,8 @@ You can utilize 'img2sixel' if you want to display graphics on your terminal
|
|
|
10
10
|
(chimp has a sixel plugin).
|
|
11
11
|
|
|
12
12
|
You can utilize 'inkscape' if you want to display svgs as sixel graphics on
|
|
13
|
-
your terminal (chimp has a sixel plugin).
|
|
13
|
+
your terminal, or include them in the PDF output (chimp has a sixel plugin).
|
|
14
|
+
|
|
15
|
+
The PDF output (chimp -o pdf) uses the DejaVu Sans Mono font if it is
|
|
16
|
+
installed. Otherwise Courier is used, which lacks some characters (e.g. box
|
|
17
|
+
drawing).
|
data/README.md
CHANGED
|
@@ -5,53 +5,100 @@ CHIMP - Cheap IMpression Presenter
|
|
|
5
5
|
___ / /___ / _ \ \ ___
|
|
6
6
|
/ __| |/ _ \ | | | | |__ \
|
|
7
7
|
| (__| | (_) | | |_| | |__) |
|
|
8
|
-
\___| |\
|
|
8
|
+
\___| |\___(_|_)____| |___/
|
|
9
9
|
\_\ /_/
|
|
10
10
|
|
|
11
11
|
Make a cheap impression. Present via terminal.
|
|
12
12
|
|
|
13
|
-
```gem install
|
|
13
|
+
```gem install cheap-impression-presenter```
|
|
14
|
+
|
|
15
|
+
The ruby gems it needs (tty-screen, pastel, hexapdf) are installed with it. Some
|
|
16
|
+
features need external programs, which have to be installed separately (all are
|
|
17
|
+
available in the usual linux distributions and homebrew):
|
|
18
|
+
|
|
19
|
+
* ``figlet``: for the figlet plugin.
|
|
20
|
+
* ``img2sixel`` (from libsixel): for the sixel plugin, i.e. pictures in the terminal.
|
|
21
|
+
Your terminal also needs sixel support.
|
|
22
|
+
* ``inkscape``: for svg pictures, in the terminal as well as in the PDF output.
|
|
23
|
+
* DejaVu Sans Mono font: for the PDF output. If it is not installed, Courier is
|
|
24
|
+
used, which lacks some characters (e.g. box drawing).
|
|
14
25
|
|
|
15
26
|
It has a simple syntax:
|
|
16
27
|
|
|
17
28
|
#what: the title of presentation
|
|
29
|
+
#size: the number of lines to zoom to (default 31, see below)
|
|
30
|
+
#theme: the PDF colors, e.g. background=#fdf6e3 text=#657b83 (see below)
|
|
31
|
+
#template: content drawn before anything else on the slide (e.g. ```figlet,right LOGO)
|
|
32
|
+
#template:
|
|
33
|
+
content drawn before anything else on the slide
|
|
34
|
+
#end
|
|
18
35
|
|
|
19
36
|
--- splits slides
|
|
20
37
|
|
|
21
38
|
+++ slides parts to appear
|
|
22
39
|
|
|
23
40
|
!!Bold Text!!
|
|
24
|
-
|
|
41
|
+
''Red Text''
|
|
25
42
|
%%Blue Text%%
|
|
43
|
+
~~Center Text
|
|
44
|
+
~~Center Text with !!Bold!!, ''Red'' and %%Blue%%
|
|
45
|
+
|
|
46
|
+
Plugins can not be used in centered lines, they have
|
|
47
|
+
their own center parameter.
|
|
26
48
|
|
|
27
49
|
Everything else is in plugins. The following plugins are existing for now:
|
|
28
50
|
|
|
29
|
-
* Sixel: display graphics in the terminal: `` ```sixel name.png``.
|
|
51
|
+
* Sixel: display graphics in the terminal: `` ```sixel,20 name.png``.
|
|
52
|
+
* The parameter 20 denotes the size in characters.
|
|
53
|
+
* You can add right or center as an additional parameter to align the picture.
|
|
54
|
+
* If the picture is right aligned and float is added as an additional parameter, text is added on the left of the picture instead of beneath.
|
|
55
|
+
* You can add animate as an additional parameter to return the cursor to where the picture started. Text that follows is then added on top of the picture instead of beneath. Combined with +++ the next picture replaces the current one, which allows for simple animations (see examples/sixel_svg.2.txt).
|
|
30
56
|
* Figlet: display fancy ascii text: `` ```figlet FANCY_TEXT``.
|
|
31
|
-
*
|
|
57
|
+
* You can add right or center as a parameter to align the fancy text.
|
|
58
|
+
* If the fancy text is right aligned, normal text is added on the left of the fancy text instead of beneath.
|
|
59
|
+
* Line Numbers: display line numbers beforce any text (mostly used for code :-).
|
|
32
60
|
~~~text
|
|
33
|
-
```
|
|
61
|
+
```lnums
|
|
34
62
|
Line
|
|
35
63
|
Line
|
|
36
64
|
Line
|
|
37
65
|
```
|
|
38
66
|
~~~
|
|
39
67
|
|
|
40
|
-
|
|
41
|
-
|
|
68
|
+
Templates should be positioned carefully (e.g. right aligned figlet or sixel) to
|
|
69
|
+
not collide with the content. All header lines (#what, #size, #theme, #template)
|
|
70
|
+
have to be at the very beginning of the presentation.
|
|
71
|
+
|
|
72
|
+
Run the presentation with:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
chimp mypresentation.txt
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
While presenting you can use the usual button for navigating. In addition, you
|
|
79
|
+
can use ``q`` to quit the presentation, and ``r`` to refresh the screen. I suggest zooming
|
|
80
|
+
the terminal until about 30 lines are visible. To make this easy a ``^`` character is
|
|
81
|
+
visible at the right end of the footer line. Zoom until the ``^`` character is no longer visible for
|
|
82
|
+
the most audience friendly results. The ``^`` is shown as long as more than 31 lines are visible,
|
|
83
|
+
use ``#size:`` directly after ``#what:`` to change this number.
|
|
42
84
|
|
|
43
85
|
Check out the examples.
|
|
44
86
|
|
|
87
|
+
How does it look?
|
|
88
|
+
=================
|
|
89
|
+
|
|
90
|
+

|
|
91
|
+
|
|
45
92
|
History & Thanks
|
|
46
93
|
================
|
|
47
94
|
|
|
48
|
-
Amy Hoy allowed me to use
|
|
95
|
+
Amy Hoy all the way back in 2009 allowed me to use her monkey ascii art for the CHIMP. The
|
|
49
96
|
CHIMP was written as an icebreaker for the curucamp, which was a ruby
|
|
50
97
|
un-conference in 2009 in Vienna. We had fun, and many cool people from the
|
|
51
98
|
community attended.
|
|
52
99
|
|
|
53
100
|
I am still using the CHIMP for scientifc presentations, and as sixel terminals
|
|
54
|
-
finally are more common, I tried to polish
|
|
101
|
+
finally are more common, I tried to polish the CHIMP up (2025) and make it available
|
|
55
102
|
for easy consumption. Maybe you like it. I use it because I can create
|
|
56
103
|
presentations faster than in PP, KN, other programs. Most of the work goes
|
|
57
104
|
into graphics anyway, which I create with dedicated tools.
|
|
@@ -59,11 +106,28 @@ into graphics anyway, which I create with dedicated tools.
|
|
|
59
106
|
Drop me a note if you want to be a contributor, or send me merge request if you
|
|
60
107
|
have created some plugins. Creating plugins is very simple :-)
|
|
61
108
|
|
|
62
|
-
|
|
63
|
-
|
|
109
|
+
PDF Output
|
|
110
|
+
==========
|
|
111
|
+
|
|
112
|
+
To send the presentation to people in one file, write it to a PDF:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
chimp -o pdf mypresentation.txt
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
This writes mypresentation.pdf to the directory you are currently in.
|
|
119
|
+
|
|
120
|
+
By default the slides are (default) 31 lines high, as if you zoomed the
|
|
121
|
+
terminal. It respects the size header. Pictures are embedded as vector
|
|
122
|
+
graphics for SVGs or as images for everything else.
|
|
123
|
+
|
|
124
|
+
The default colors are solarized light. Change them with a ``#theme:`` line in
|
|
125
|
+
the header, holding KEY=#RRGGBB pairs for background, text, bold, red, blue and
|
|
126
|
+
footer. Colors you leave out keep their default. Solarized dark for example:
|
|
127
|
+
|
|
128
|
+
#theme: background=#002b36 text=#839496 bold=#93a1a1 red=#dc322f blue=#268bd2 footer=#586e75
|
|
64
129
|
|
|
65
|
-
|
|
66
|
-
to people in one file.
|
|
130
|
+
examples/chimp.txt (light) and examples/markus.txt (dark) show both.
|
|
67
131
|
|
|
68
132
|
LICENSE
|
|
69
133
|
=======
|
data/chimp.gemspec
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Gem::Specification.new do |s|
|
|
2
2
|
s.name = "cheap-impression-presenter"
|
|
3
|
-
s.version = "1.
|
|
3
|
+
s.version = "1.1.0"
|
|
4
4
|
s.platform = Gem::Platform::RUBY
|
|
5
5
|
s.license = "GPL-3.0-or-later"
|
|
6
6
|
s.summary = "CHIMP - Make a cheap impression. Present via terminal."
|
|
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
|
|
|
22
22
|
|
|
23
23
|
s.add_runtime_dependency 'tty-screen', '~> 0.8'
|
|
24
24
|
s.add_runtime_dependency 'pastel', '~> 0.8'
|
|
25
|
+
s.add_runtime_dependency 'hexapdf', '~> 1.0'
|
|
25
26
|
end
|
data/lib/ChimpParser-Grammar.rb
CHANGED
|
@@ -10,6 +10,10 @@ module Chimp
|
|
|
10
10
|
|
|
11
11
|
P_SLIDES = Pattern.new("P_SLIDES", /---+.*?(\z|\n)|\A/, /^(?=---+)|\z/)
|
|
12
12
|
P_WHAT = Pattern.new("P_WHAT", /^#\s*what:\s*/, /[\t ]*(\n|\z)/)
|
|
13
|
+
P_SIZE = Pattern.new("P_SIZE", /^#\s*size:\s*/, /[\t ]*(\n|\z)/)
|
|
14
|
+
P_THEME = Pattern.new("P_THEME", /^#\s*theme:\s*/, /[\t ]*(\n|\z)/)
|
|
15
|
+
P_TEMPLATES = Pattern.new("P_TEMPLATE", /^#\s*template:[\t ]*\n/, /^#\s*end[\t ]*(\n|\z)/)
|
|
16
|
+
P_TEMPLATE = Pattern.new("P_TEMPLATE", /^#\s*template:[\t ]*/, /[\t ]*(\n|\z)/)
|
|
13
17
|
P_INCREMENTAL = Pattern.new("P_INCREMENTAL", /\+\+\++.*?(\z|\n)|\A/, /^(?=\+\+\++)|\z/)
|
|
14
18
|
P_RANGE = Pattern.new("P_RANGE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]*/, /^```[\t ]*(\n|\z)/)
|
|
15
19
|
P_INCLUDE = Pattern.new("P_INCLUDE", /^```([a-z0-9_]+,)*[a-z0-9_]+[\t ]+/, /[\t ]*(\n|\z)/)
|
|
@@ -25,16 +29,13 @@ module Chimp
|
|
|
25
29
|
#### G + patternname is selected to look up nested patterns ####
|
|
26
30
|
#### If G + patternname not exists, [] is assumend ####
|
|
27
31
|
|
|
28
|
-
ROOT = [ P_WHAT, P_SLIDES ]
|
|
32
|
+
ROOT = [ P_WHAT, P_SIZE, P_THEME, P_TEMPLATES, P_TEMPLATE, P_SLIDES ]
|
|
29
33
|
GP_SLIDES = [ P_INCREMENTAL ]
|
|
30
34
|
GP_INCREMENTAL = [ P_CENTER, P_RANGE, P_INCLUDE, P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
|
|
35
|
+
GP_TEMPLATE = GP_INCREMENTAL
|
|
36
|
+
GP_CENTER = [ P_STRONGRED, P_STRONGBLUE, P_STRONG, P_RED, P_BLUE ]
|
|
31
37
|
|
|
32
38
|
### Optional functions called when a pattern occurs (m + patternname) ####
|
|
33
|
-
def mP_CENTER(ts,ti,te)
|
|
34
|
-
@tree.last.data = ti.length
|
|
35
|
-
ti
|
|
36
|
-
end
|
|
37
|
-
|
|
38
39
|
def mP_INCLUDE(ts,ti,te)
|
|
39
40
|
@tree.last.data = {}
|
|
40
41
|
@tree.last.data[:name] = ts[3..-1].strip.split(',')[0]
|
|
@@ -54,6 +55,14 @@ module Chimp
|
|
|
54
55
|
@tree.last.data = ti
|
|
55
56
|
''
|
|
56
57
|
end
|
|
58
|
+
def mP_SIZE(ts,ti,te)
|
|
59
|
+
@tree.last.data = ti.to_i
|
|
60
|
+
''
|
|
61
|
+
end
|
|
62
|
+
def mP_THEME(ts,ti,te)
|
|
63
|
+
@tree.last.data = ti
|
|
64
|
+
''
|
|
65
|
+
end
|
|
57
66
|
end
|
|
58
67
|
|
|
59
68
|
end
|
data/lib/ChimpParser.rb
CHANGED
|
@@ -38,6 +38,35 @@ module Chimp
|
|
|
38
38
|
def string(data)
|
|
39
39
|
data
|
|
40
40
|
end
|
|
41
|
+
|
|
42
|
+
#### calls the output method for element i of the tree, raises NameError if there is none
|
|
43
|
+
def dispatch(tree,i)
|
|
44
|
+
c = tree[i]
|
|
45
|
+
met, data = case c
|
|
46
|
+
when String then [method("string"), c]
|
|
47
|
+
when OpenTag then [method("mO" + c.ttype), c.data]
|
|
48
|
+
when CloseTag then [method("mC" + c.ttype), tree[c.open].data]
|
|
49
|
+
end
|
|
50
|
+
case met.arity
|
|
51
|
+
when 0; met.call
|
|
52
|
+
when 1; met.call(data)
|
|
53
|
+
when 2; met.call(c,tree)
|
|
54
|
+
when 3; met.call(c,tree,i)
|
|
55
|
+
when 4; met.call(c,tree,i,data)
|
|
56
|
+
else
|
|
57
|
+
""
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#### outputs a part of the tree again (e.g. a template for every slide)
|
|
62
|
+
def replay(tree,from,to)
|
|
63
|
+
from.upto(to) do |i|
|
|
64
|
+
begin
|
|
65
|
+
dispatch(tree,i)
|
|
66
|
+
rescue NameError
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
41
70
|
end
|
|
42
71
|
|
|
43
72
|
class Grammar
|
|
@@ -83,26 +112,7 @@ module Chimp
|
|
|
83
112
|
while @tree.length > i
|
|
84
113
|
c = @tree[i]
|
|
85
114
|
begin
|
|
86
|
-
|
|
87
|
-
when c.class == String
|
|
88
|
-
data = c
|
|
89
|
-
output.method("string")
|
|
90
|
-
when c.class == OpenTag
|
|
91
|
-
data = c.data
|
|
92
|
-
output.method("mO" + c.ttype)
|
|
93
|
-
when c.class == CloseTag
|
|
94
|
-
data = @tree[c.open].data
|
|
95
|
-
output.method("mC" + c.ttype)
|
|
96
|
-
end
|
|
97
|
-
out << case met.arity
|
|
98
|
-
when 0; met.call
|
|
99
|
-
when 1; met.call(data)
|
|
100
|
-
when 2; met.call(c,@tree)
|
|
101
|
-
when 3; met.call(c,@tree,i)
|
|
102
|
-
when 4; met.call(c,@tree,i,data)
|
|
103
|
-
else
|
|
104
|
-
""
|
|
105
|
-
end.to_s
|
|
115
|
+
out << output.dispatch(@tree,i).to_s
|
|
106
116
|
|
|
107
117
|
i += 1
|
|
108
118
|
rescue NameError => e
|
data/lib/ChimpPlugin.rb
CHANGED
data/lib/output/pdf.rb
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require 'hexapdf'
|
|
2
|
+
require 'fileutils'
|
|
3
|
+
require_relative 'screen'
|
|
4
|
+
|
|
5
|
+
module Chimp
|
|
6
|
+
class Parser
|
|
7
|
+
|
|
8
|
+
class Pdf < Screen
|
|
9
|
+
#### solarized light, #theme: in the header overrides single colors, e.g.
|
|
10
|
+
#### #theme: background=#002b36 text=#839496 bold=#93a1a1 footer=#586e75
|
|
11
|
+
THEME = {
|
|
12
|
+
:background => 'fdf6e3', :text => '657b83', :bold => '586e75',
|
|
13
|
+
:red => 'dc322f', :blue => '268bd2', :footer => '93a1a1'
|
|
14
|
+
}
|
|
15
|
+
FONTS = [
|
|
16
|
+
%w(/usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono.ttf /usr/share/fonts/dejavu-sans-mono-fonts/DejaVuSansMono-Bold.ttf),
|
|
17
|
+
%w(/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf /usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf),
|
|
18
|
+
%w(/usr/share/fonts/TTF/DejaVuSansMono.ttf /usr/share/fonts/TTF/DejaVuSansMono-Bold.ttf),
|
|
19
|
+
%w(/Library/Fonts/DejaVuSansMono.ttf /Library/Fonts/DejaVuSansMono-Bold.ttf)
|
|
20
|
+
]
|
|
21
|
+
WIDTH = 960 # 16:9 page in points
|
|
22
|
+
HEIGHT = 540
|
|
23
|
+
MARGIN = 20
|
|
24
|
+
|
|
25
|
+
#### a virtual terminal that collects what is printed into a character grid {{{
|
|
26
|
+
class Terminal
|
|
27
|
+
attr_reader :columns, :lines, :images
|
|
28
|
+
|
|
29
|
+
def initialize(columns,lines,pdf)
|
|
30
|
+
@columns = columns
|
|
31
|
+
@lines = lines
|
|
32
|
+
@pdf = pdf
|
|
33
|
+
clear
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def clear
|
|
37
|
+
@grid = Array.new(@lines) { Array.new(@columns) }
|
|
38
|
+
@images = []
|
|
39
|
+
@x = @y = 1
|
|
40
|
+
@bold = false
|
|
41
|
+
@color = nil
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def set_pos(x,y)
|
|
46
|
+
@x = x < 1 ? 1 : x
|
|
47
|
+
@y = y < 1 ? 1 : y
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
def get_pos
|
|
51
|
+
[@x, @y]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def print(a)
|
|
55
|
+
a.to_s.scan(/\e\[([\d;]*)m|(\r?\n)|(.)/m) do |sgr,nl,char|
|
|
56
|
+
if sgr
|
|
57
|
+
sgr.split(';').map(&:to_i).each do |code|
|
|
58
|
+
case code
|
|
59
|
+
when 0 then @bold = false; @color = nil
|
|
60
|
+
when 1 then @bold = true
|
|
61
|
+
when 31 then @color = :red
|
|
62
|
+
when 34 then @color = :blue
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
elsif nl
|
|
66
|
+
@x = 1
|
|
67
|
+
@y += 1
|
|
68
|
+
else
|
|
69
|
+
if @x > @columns # deferred autowrap, like a real terminal
|
|
70
|
+
@x = 1
|
|
71
|
+
@y += 1
|
|
72
|
+
end
|
|
73
|
+
@grid[@y-1][@x-1] = [char, @bold, @color] if @y <= @lines
|
|
74
|
+
@x += 1
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
### the image is placed at the cursor, the cursor moves below it
|
|
80
|
+
def image(what,width,fname)
|
|
81
|
+
img, rows = @pdf.load_image(what,width,fname)
|
|
82
|
+
return print("Image #{what} not found.\n") if img.nil?
|
|
83
|
+
@images << { :file => img, :x => @x, :y => @y, :width => width }
|
|
84
|
+
@y += rows
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
### yields runs of equally formatted text: x, y, text, bold, color
|
|
88
|
+
def each_run
|
|
89
|
+
@grid.each_with_index do |row,y|
|
|
90
|
+
x = 0
|
|
91
|
+
while x < row.length
|
|
92
|
+
if row[x].nil? || row[x][0] == ' '
|
|
93
|
+
x += 1
|
|
94
|
+
next
|
|
95
|
+
end
|
|
96
|
+
start = x
|
|
97
|
+
text = ''
|
|
98
|
+
while x < row.length && row[x] && row[x][1..2] == row[start][1..2]
|
|
99
|
+
text << row[x][0]
|
|
100
|
+
x += 1
|
|
101
|
+
end
|
|
102
|
+
yield start + 1, y + 1, text.rstrip, row[start][1], row[start][2]
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end #}}}
|
|
107
|
+
|
|
108
|
+
#### the pdf is written next to the presentation: talk.txt -> talk.pdf
|
|
109
|
+
def initialize(fname) #{{{
|
|
110
|
+
@scounter = @ccounter = 0
|
|
111
|
+
@what = ''
|
|
112
|
+
@size = 31
|
|
113
|
+
@last = 0
|
|
114
|
+
@skip = -1
|
|
115
|
+
@templates = []
|
|
116
|
+
|
|
117
|
+
@fname = File.basename(fname,'.*') + '.pdf'
|
|
118
|
+
@theme = THEME.dup
|
|
119
|
+
@doc = HexaPDF::Document.new
|
|
120
|
+
@doc.config['font.on_missing_glyph'] = proc { |char,font| font.decode_utf8('?').first }
|
|
121
|
+
regular, bold = FONTS.find { |f| f.all? { |e| File.exist?(e) } }
|
|
122
|
+
@font = @doc.fonts.add(regular || 'Courier')
|
|
123
|
+
@bold = @doc.fonts.add(bold || 'Courier', **(bold ? {} : { :variant => :bold }))
|
|
124
|
+
@page = nil
|
|
125
|
+
end #}}}
|
|
126
|
+
|
|
127
|
+
#### the slide always has @size lines, the last two of which are the footer
|
|
128
|
+
def finish_prepare #{{{
|
|
129
|
+
@rowh = (HEIGHT - 2 * MARGIN).to_f / @size
|
|
130
|
+
@fsize = @rowh / 1.2
|
|
131
|
+
@cw = @font.decode_utf8('M').first.width * @fsize / 1000.0
|
|
132
|
+
columns = ((WIDTH - 2 * MARGIN) / @cw).floor
|
|
133
|
+
@left = (WIDTH - columns * @cw) / 2.0
|
|
134
|
+
@screen = Terminal.new(columns, @size - 2, self)
|
|
135
|
+
end #}}}
|
|
136
|
+
|
|
137
|
+
def finish_output #{{{
|
|
138
|
+
@doc.write(@fname, :optimize => true)
|
|
139
|
+
nil
|
|
140
|
+
end #}}}
|
|
141
|
+
|
|
142
|
+
def mPP_THEME(data) #{{{
|
|
143
|
+
data.split.each do |pair|
|
|
144
|
+
key, color = pair.split('=',2)
|
|
145
|
+
unless THEME.key?(key.to_sym) && color =~ /\A#?(\h{6})\z/
|
|
146
|
+
abort "Invalid #theme: entry '#{pair}', use KEY=#RRGGBB with KEY one of: #{THEME.keys.join(', ')}"
|
|
147
|
+
end
|
|
148
|
+
@theme[key.to_sym] = $1
|
|
149
|
+
end
|
|
150
|
+
end #}}}
|
|
151
|
+
|
|
152
|
+
def mOP_SLIDES(c,tree)
|
|
153
|
+
@page = c.userdata
|
|
154
|
+
@screen.clear
|
|
155
|
+
template tree
|
|
156
|
+
end
|
|
157
|
+
#### every +++ step (animation frame) becomes a page of its own
|
|
158
|
+
def mOP_INCREMENTAL(c,tree); end
|
|
159
|
+
def mCP_INCREMENTAL(c,tree,i)
|
|
160
|
+
render
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#### plugins only need to prepare things for the terminal
|
|
164
|
+
def mPP_INCLUDE(data); end
|
|
165
|
+
def mPP_RANGE(data); end
|
|
166
|
+
|
|
167
|
+
#### an image in the pdf, svgs are converted to pdf with inkscape (cached)
|
|
168
|
+
def load_image(what,width,fname) #{{{
|
|
169
|
+
src = File.join(File.dirname(fname),what)
|
|
170
|
+
return unless File.exist?(src)
|
|
171
|
+
if what =~ /\.svg$/
|
|
172
|
+
cache = File.join(File.dirname(fname),'.' + File.basename(fname,'.*'),"#{what.gsub('/','_')}.pdf")
|
|
173
|
+
unless File.exist?(cache) && File.mtime(cache) >= File.mtime(src)
|
|
174
|
+
FileUtils.mkdir_p(File.dirname(cache))
|
|
175
|
+
system 'inkscape', '-D', '--export-type=pdf', src, '-o', cache, :err => File::NULL
|
|
176
|
+
end
|
|
177
|
+
src = cache
|
|
178
|
+
end
|
|
179
|
+
img = @doc.images.add(src)
|
|
180
|
+
w, h = img.is_a?(HexaPDF::Type::Form) ? [img.box.width, img.box.height] : [img.width, img.height]
|
|
181
|
+
[img, (width * @cw * h / w / @rowh).ceil]
|
|
182
|
+
end #}}}
|
|
183
|
+
|
|
184
|
+
def render #{{{
|
|
185
|
+
return if @page.nil?
|
|
186
|
+
canvas = @doc.pages.add([0, 0, WIDTH, HEIGHT]).canvas
|
|
187
|
+
canvas.fill_color(@theme[:background]).rectangle(0, 0, WIDTH, HEIGHT).fill
|
|
188
|
+
|
|
189
|
+
### images first, text that follows goes on top. an image covers
|
|
190
|
+
### what was below (e.g. for animations), so paint the background first.
|
|
191
|
+
@screen.images.each do |i|
|
|
192
|
+
x = @left + (i[:x] - 1) * @cw
|
|
193
|
+
w = i[:width] * @cw
|
|
194
|
+
obj = i[:file]
|
|
195
|
+
h = w * (obj.is_a?(HexaPDF::Type::Form) ? obj.box.height / obj.box.width : obj.height.to_f / obj.width)
|
|
196
|
+
y = top(i[:y]) - h
|
|
197
|
+
canvas.fill_color(@theme[:background]).rectangle(x, y, w, h).fill
|
|
198
|
+
canvas.image(obj, :at => [x, y], :width => w)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
@screen.each_run do |x,y,text,bold,color|
|
|
202
|
+
canvas.font(bold ? @bold : @font, :size => @fsize)
|
|
203
|
+
canvas.fill_color(@theme[color || (bold ? :bold : :text)])
|
|
204
|
+
canvas.text(text, :at => [@left + (x - 1) * @cw, baseline(y)])
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
### footer
|
|
208
|
+
canvas.stroke_color(@theme[:footer]).line_width(0.5)
|
|
209
|
+
canvas.line(@left, top(@size - 1) - @rowh / 2, @left + @screen.columns * @cw, top(@size - 1) - @rowh / 2).stroke
|
|
210
|
+
canvas.font(@font, :size => @fsize).fill_color(@theme[:footer])
|
|
211
|
+
canvas.text(@what, :at => [@left, baseline(@size)])
|
|
212
|
+
num = "#{@page}/#{@scounter}"
|
|
213
|
+
canvas.text(num, :at => [@left + (@screen.columns - num.length) * @cw, baseline(@size)])
|
|
214
|
+
end #}}}
|
|
215
|
+
|
|
216
|
+
def top(y)
|
|
217
|
+
HEIGHT - MARGIN - (y - 1) * @rowh
|
|
218
|
+
end
|
|
219
|
+
def baseline(y)
|
|
220
|
+
top(y) - (@rowh + @fsize * 0.6) / 2
|
|
221
|
+
end
|
|
222
|
+
private :render, :top, :baseline
|
|
223
|
+
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
data/lib/output/screen.rb
CHANGED
|
@@ -70,7 +70,7 @@ module Window #{{{
|
|
|
70
70
|
end
|
|
71
71
|
m = res.match /(?<column>\d+);(?<row>\d+)/
|
|
72
72
|
[ Integer(m[:row]), Integer(m[:column]) ]
|
|
73
|
-
rescue
|
|
73
|
+
rescue WinchError
|
|
74
74
|
[0,0]
|
|
75
75
|
end
|
|
76
76
|
end #}}}
|
|
@@ -83,11 +83,13 @@ module Chimp
|
|
|
83
83
|
#### mO + patternname for opening tags ####
|
|
84
84
|
#### mC + patternname for closing tags ####
|
|
85
85
|
|
|
86
|
-
def initialize() #{{{
|
|
86
|
+
def initialize(fname) #{{{
|
|
87
87
|
@scounter = @ccounter = 0
|
|
88
88
|
@what = ''
|
|
89
|
+
@size = 31
|
|
89
90
|
@last = 0
|
|
90
91
|
@skip = -1
|
|
92
|
+
@templates = []
|
|
91
93
|
|
|
92
94
|
@format = Pastel.new
|
|
93
95
|
@screen = Window
|
|
@@ -95,6 +97,11 @@ module Chimp
|
|
|
95
97
|
@screen.init
|
|
96
98
|
end #}}}
|
|
97
99
|
|
|
100
|
+
def template(tree)
|
|
101
|
+
@templates.each { |from,to| replay tree, from, to }
|
|
102
|
+
@screen.set_pos 0, 0
|
|
103
|
+
end
|
|
104
|
+
|
|
98
105
|
def finish_output #{{{
|
|
99
106
|
@screen.set_pos 0, @screen.lines
|
|
100
107
|
@screen.print ' ' * (@screen.columns - 5)
|
|
@@ -104,6 +111,9 @@ module Chimp
|
|
|
104
111
|
def mPP_WHAT(data)
|
|
105
112
|
@what = data
|
|
106
113
|
end
|
|
114
|
+
def mPP_SIZE(data)
|
|
115
|
+
@size = data if data > 0
|
|
116
|
+
end
|
|
107
117
|
|
|
108
118
|
def mPP_SLIDES(c,tree)
|
|
109
119
|
#{{{
|
|
@@ -117,21 +127,26 @@ module Chimp
|
|
|
117
127
|
@screen.clear
|
|
118
128
|
lines = @screen.lines
|
|
119
129
|
columns = @screen.columns
|
|
120
|
-
### print mark
|
|
121
|
-
@screen.set_pos columns-1, 30
|
|
122
|
-
@screen.print "\e[1;37m^\e[0m"
|
|
123
|
-
### print footer
|
|
124
130
|
@screen.set_pos 0, lines-1
|
|
125
|
-
|
|
131
|
+
### print footer, with a mark at the end
|
|
132
|
+
@screen.print (lines > @size ? ("-"*(columns-1) + "^") : "-"*columns)
|
|
126
133
|
@screen.set_pos 0, lines
|
|
127
134
|
@screen.print @what
|
|
128
135
|
num = "#{c.userdata}/#{@scounter}"
|
|
129
|
-
@screen.set_pos columns-num.length, lines
|
|
136
|
+
@screen.set_pos columns-num.length+1, lines
|
|
130
137
|
@screen.print num
|
|
131
138
|
@screen.set_pos 0, 0
|
|
139
|
+
template tree
|
|
132
140
|
#}}}
|
|
133
141
|
end
|
|
134
142
|
|
|
143
|
+
def mPP_TEMPLATE(c,tree,i)
|
|
144
|
+
@templates << [i+1, c.close-1]
|
|
145
|
+
end
|
|
146
|
+
def mOP_TEMPLATE(data)
|
|
147
|
+
raise TagSkipEvent
|
|
148
|
+
end
|
|
149
|
+
|
|
135
150
|
def mPP_INCREMENTAL(c,tree,i)
|
|
136
151
|
@last = c.close
|
|
137
152
|
end
|
|
@@ -142,6 +157,9 @@ module Chimp
|
|
|
142
157
|
def mCP_INCREMENTAL(c,tree,i)
|
|
143
158
|
return if @skip > i
|
|
144
159
|
@skip = -1
|
|
160
|
+
### on the first part of the first slide there is nothing to go back to
|
|
161
|
+
pos = c.open-1
|
|
162
|
+
first = tree[pos].ttype == "P_SLIDES" && tree[0...pos].none? { |t| t.class == OpenTag && t.ttype == "P_SLIDES" }
|
|
145
163
|
begin
|
|
146
164
|
if @last == i
|
|
147
165
|
lines = @screen.lines
|
|
@@ -154,18 +172,13 @@ module Chimp
|
|
|
154
172
|
rescue Window::WinchError
|
|
155
173
|
ch = :refresh
|
|
156
174
|
end
|
|
157
|
-
end while @last == i && ![:previous, :exit, :refresh].include?(ch)
|
|
175
|
+
end while (first && ch == :previous) || (@last == i && ![:previous, :exit, :refresh].include?(ch))
|
|
158
176
|
case ch
|
|
159
177
|
when :previous
|
|
160
178
|
#{{{
|
|
161
|
-
pos = c.open-1
|
|
162
179
|
tag = tree[pos]
|
|
163
180
|
if tag.ttype == "P_SLIDES"
|
|
164
|
-
|
|
165
|
-
raise TagMoveEvent, pos
|
|
166
|
-
else
|
|
167
|
-
raise TagMoveEvent, tree[pos-1].open
|
|
168
|
-
end
|
|
181
|
+
raise TagMoveEvent, tree[pos-1].open
|
|
169
182
|
end
|
|
170
183
|
if tag.ttype == "P_INCREMENTAL"
|
|
171
184
|
pos = tag.open
|
|
@@ -176,7 +189,7 @@ module Chimp
|
|
|
176
189
|
#### how many spaces needed
|
|
177
190
|
howmuch = ((y - tag.userdata[:y] + 1) * columns) - (columns - x) - tag.userdata[:x]
|
|
178
191
|
@screen.set_pos tag.userdata[:x], tag.userdata[:y]
|
|
179
|
-
@screen.print x.to_s + ',' + y.to_s
|
|
192
|
+
# @screen.print x.to_s + ',' + y.to_s
|
|
180
193
|
@screen.print ' ' * howmuch
|
|
181
194
|
@screen.set_pos tag.userdata[:x], tag.userdata[:y]
|
|
182
195
|
####
|
|
@@ -197,6 +210,15 @@ module Chimp
|
|
|
197
210
|
exit
|
|
198
211
|
end
|
|
199
212
|
end
|
|
213
|
+
def mPP_INCLUDE(data)
|
|
214
|
+
require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
|
|
215
|
+
eval('Chimp::Plugin::' + data[:name].upcase).new.prepare(data[:what],data[:parameters],data[:additional])
|
|
216
|
+
end
|
|
217
|
+
def mPP_RANGE(data)
|
|
218
|
+
require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
|
|
219
|
+
eval('Chimp::Plugin::' + data[:name].upcase).new.prepare(data[:what],data[:parameters],data[:additional])
|
|
220
|
+
end
|
|
221
|
+
|
|
200
222
|
def mOP_INCLUDE(data)
|
|
201
223
|
require File::dirname(__FILE__) + "/../plugins/#{data[:name]}.rb"
|
|
202
224
|
eval('Chimp::Plugin::' + data[:name].upcase).new.process(data[:what],data[:parameters],@screen,data[:additional])
|
|
@@ -217,7 +239,9 @@ module Chimp
|
|
|
217
239
|
def mOP_STRONGBLUE(data); @screen.print "\e[1;34m"; end
|
|
218
240
|
def mCP_STRONGBLUE(data); @screen.print "\e[0m"; end
|
|
219
241
|
|
|
220
|
-
def mOP_CENTER(
|
|
242
|
+
def mOP_CENTER(c,tree,i)
|
|
243
|
+
### only the visible text counts, not the markup
|
|
244
|
+
data = tree[i+1...c.close].grep(String).sum(&:length)
|
|
221
245
|
len = @screen.columns
|
|
222
246
|
if len-data > 0
|
|
223
247
|
@screen.print ' ' * ((len - data) / 2)
|
data/lib/plugins/sixel.rb
CHANGED
|
@@ -1,31 +1,62 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
|
|
1
3
|
module Chimp
|
|
2
4
|
module Plugin
|
|
3
5
|
class SIXEL < Base
|
|
6
|
+
# converted images are cached in .PRESENTATIONNAME/ next to the presentation
|
|
7
|
+
def cache_file(what,width,fname)
|
|
8
|
+
dir = File.join(File.dirname(fname),'.' + File.basename(fname,'.*'))
|
|
9
|
+
File.join(dir,"#{what.gsub('/','_')}.#{width}.sixel")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def sixel(what,width,fname)
|
|
13
|
+
src = File.join(File.dirname(fname),what)
|
|
14
|
+
cache = cache_file(what,width,fname)
|
|
15
|
+
if File.exist?(cache) && File.exist?(src) && File.mtime(cache) >= File.mtime(src)
|
|
16
|
+
return File.read(cache)
|
|
17
|
+
end
|
|
18
|
+
convert = if what =~ /\.svg$/
|
|
19
|
+
"inkscape -D -d 300 --export-type=png #{src} -o - | img2sixel -w #{width*10}"
|
|
20
|
+
else
|
|
21
|
+
"img2sixel -w #{width*10} #{src}"
|
|
22
|
+
end
|
|
23
|
+
result = `#{convert}`
|
|
24
|
+
if $?.success? && !result.empty?
|
|
25
|
+
FileUtils.mkdir_p(File.dirname(cache))
|
|
26
|
+
File.write(cache,result)
|
|
27
|
+
end
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def prepare(what,parameters,additional)
|
|
32
|
+
return if parameters[0] !~ /\d/
|
|
33
|
+
sixel(what,parameters[0].to_i,additional[:fname])
|
|
34
|
+
end
|
|
35
|
+
|
|
4
36
|
def process(what,parameters,screen,additional)
|
|
5
37
|
if parameters[0] !~ /\d/
|
|
6
38
|
screen.print 'Use ```sixel,NUM with NUM beeing the width if the picture in characters.'
|
|
7
39
|
return
|
|
8
40
|
end
|
|
9
41
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"img2sixel -w #{parameters[0].to_i*10} #{File.join(File.dirname(additional[:fname]),what)}"
|
|
14
|
-
end
|
|
42
|
+
restore = parameters.include?('animate') || parameters.include?('float')
|
|
43
|
+
pos = screen.get_pos if restore
|
|
44
|
+
len = screen.columns
|
|
15
45
|
if parameters.include? 'right'
|
|
16
|
-
pos = screen.get_pos
|
|
17
|
-
len = screen.columns
|
|
18
46
|
screen.print " " * (len-parameters[0].to_i)
|
|
19
|
-
screen.print `#{printit}`
|
|
20
|
-
screen.set_pos *pos
|
|
21
47
|
elsif parameters.include? 'center'
|
|
22
|
-
len = screen.columns
|
|
23
48
|
screen.print " " * ((len-parameters[0].to_i)/2)
|
|
24
|
-
|
|
49
|
+
end
|
|
50
|
+
if screen.respond_to?(:image) # pdf output
|
|
51
|
+
screen.image what, parameters[0].to_i, additional[:fname]
|
|
52
|
+
else
|
|
53
|
+
screen.print sixel(what,parameters[0].to_i,additional[:fname])
|
|
54
|
+
end
|
|
55
|
+
if restore
|
|
56
|
+
screen.set_pos *pos
|
|
57
|
+
else
|
|
25
58
|
x, y = screen.get_pos
|
|
26
59
|
screen.set_pos 0,y
|
|
27
|
-
else
|
|
28
|
-
screen.print `#{printit}`
|
|
29
60
|
end
|
|
30
61
|
end
|
|
31
62
|
end
|
data/tools/chimp
CHANGED
|
@@ -8,22 +8,26 @@ require 'optparse'
|
|
|
8
8
|
require_relative '../lib/ChimpParser'
|
|
9
9
|
require_relative '../lib/ChimpPlugin'
|
|
10
10
|
require_relative '../lib/ChimpParser-Grammar'
|
|
11
|
-
require_relative '../lib/output/screen'
|
|
12
11
|
|
|
12
|
+
output = 'screen'
|
|
13
|
+
outputs = Dir[File.join(__dir__,'..','lib','output','*.rb')].map { |f| File.basename(f,'.rb') }.sort
|
|
13
14
|
ARGV.options { |opt|
|
|
14
15
|
opt.summary_indent = ' ' * 2
|
|
15
16
|
opt.banner = "Usage:\n#{opt.summary_indent}#{File.basename($0)} [options] [FILENAME]\n"
|
|
16
17
|
opt.on("Options:")
|
|
17
18
|
opt.on("--help", "-h", "This text") { puts opt; exit }
|
|
19
|
+
opt.on("--output=NAME", "-o NAME", outputs, "Output: #{outputs.join(', ')} (default: screen)") { |o| output = o }
|
|
18
20
|
opt.on("Filename needs to be a chimp presentation.")
|
|
19
21
|
opt.parse!
|
|
20
|
-
}
|
|
22
|
+
} or exit 1 # nil on invalid options
|
|
21
23
|
if ARGV.length == 0 || !File.exist?(ARGV[0])
|
|
22
24
|
puts ARGV.options
|
|
23
25
|
exit
|
|
24
26
|
end
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
fname = File.realpath(ARGV[0])
|
|
29
|
+
grammy = Chimp::Parser::SimpleGrammar.parse File::read(fname), :fname => fname
|
|
30
|
+
require_relative "../lib/output/#{output}"
|
|
31
|
+
screen = Chimp::Parser.const_get(output.capitalize).new fname
|
|
28
32
|
grammy.prepare screen
|
|
29
33
|
grammy.output screen
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cheap-impression-presenter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Juergen eTM Mangler
|
|
8
8
|
bindir: tools
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: tty-screen
|
|
@@ -37,14 +37,28 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '0.8'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: hexapdf
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.0'
|
|
40
54
|
description: see https://github.com/etm/chimp
|
|
41
55
|
email: juergen.mangler@gmail.com
|
|
42
56
|
executables:
|
|
43
57
|
- chimp
|
|
44
58
|
extensions: []
|
|
45
59
|
extra_rdoc_files:
|
|
46
|
-
- README.md
|
|
47
60
|
- INSTALL.md
|
|
61
|
+
- README.md
|
|
48
62
|
files:
|
|
49
63
|
- AUTHORS
|
|
50
64
|
- INSTALL.md
|
|
@@ -55,6 +69,7 @@ files:
|
|
|
55
69
|
- lib/ChimpParser-Grammar.rb
|
|
56
70
|
- lib/ChimpParser.rb
|
|
57
71
|
- lib/ChimpPlugin.rb
|
|
72
|
+
- lib/output/pdf.rb
|
|
58
73
|
- lib/output/screen.rb
|
|
59
74
|
- lib/plugins/figlet.rb
|
|
60
75
|
- lib/plugins/lnums.rb
|
|
@@ -78,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
93
|
- !ruby/object:Gem::Version
|
|
79
94
|
version: '0'
|
|
80
95
|
requirements: []
|
|
81
|
-
rubygems_version: 3.6.
|
|
96
|
+
rubygems_version: 3.6.9
|
|
82
97
|
specification_version: 4
|
|
83
98
|
summary: CHIMP - Make a cheap impression. Present via terminal.
|
|
84
99
|
test_files: []
|