tty_slides 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in tty_slides.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mark Lorenz
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # TTY Slides
2
+
3
+ > Gem for presenting slides with the terminal
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ gem install tty_slides
9
+ ```
10
+
11
+ ## Usage
12
+ ```ruby
13
+ # define the BASE_PATH and SLIDES
14
+ class SlideList
15
+ BASE_PATH = Pathname.new(ROOT) + ".." + "slides"
16
+
17
+ SLIDES = [
18
+ "introduction",
19
+ "covermymeds",
20
+ ]
21
+ end
22
+ ```
23
+
24
+ ``bash
25
+
26
+ ```
27
+
28
+ ## Adding Slides
29
+ Slides are stored in bastardized version of HTML that can be rendred with n-curses. They are found in the `doc/` folder
30
+ ### CML specification
31
+ - `<head>`: the text node in this element is positioned at the top left of the screen.
32
+ - `<body>`: contains any number of `<div>`, `<span>`, `<code>` or `<line>` tags.
33
+ - `<div>` and `<span>`: go togeather to make a vertially and horizontally centered line. Each div can be no longer than one line.
34
+ - `<code>` and `<line>`: go togeather to make left aligned text that is in a horizontally and vertically centered box. Each line, should be one line. Duh.
35
+ - attributes must be curses attribute calls e.g.
36
+ `Curses.color_pair(RED_COLOR)` becomes `color_pair(RED_COLOR)`
37
+
38
+ ## Contributing
39
+
40
+ 1. Fork it
41
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
42
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
43
+ 4. Push to the branch (`git push origin my-new-feature`)
44
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/cml/cml.rb ADDED
@@ -0,0 +1,123 @@
1
+ require 'nokogiri'
2
+ class CML
3
+ PADDING = 2
4
+
5
+ attr_accessor :window
6
+ attr_accessor :text
7
+
8
+ def initialize window, text
9
+ @window = window
10
+ @text = text
11
+ @document = parse
12
+ end
13
+
14
+ def render
15
+ @window.clear
16
+ render_loop @document
17
+ @window.refresh
18
+ end
19
+
20
+ private
21
+
22
+ def vertically_center(fragment)
23
+ top_line = (@window.maxy - fragment.css('div,line').size) / 2 - PADDING
24
+ @window.setpos(top_line, @window.curx)
25
+ end
26
+
27
+ def horizontally_center(text)
28
+ left_pos = (@window.maxx - text.size) / 2
29
+ @window.setpos(@window.cury+1, left_pos)
30
+ end
31
+
32
+ def left_align(fragment)
33
+ left_pos = PADDING
34
+ @current_offset ||= 0
35
+ @window.setpos(@window.cury + 1, left_pos + @current_offset)
36
+ end
37
+
38
+ def offset_for_code_box(fragment)
39
+ largest_fragment = fragment.css('line').map(&:text).max_by(&:length)
40
+ @current_offset = (@window.maxx - largest_fragment.size) / 2
41
+ end
42
+
43
+ # DFS
44
+ def render_loop(fragment)
45
+ recurse = ->(){
46
+ fragment.children.each do |child|
47
+ render_loop(child)
48
+ end
49
+ }
50
+
51
+ if fragment.node_name == "div"
52
+ div fragment[:style], fragment.text, &recurse
53
+ elsif fragment.node_name == "title"
54
+ title fragment[:style], fragment.text, &recurse
55
+ elsif fragment.node_name == "body"
56
+ body fragment[:style], fragment, &recurse
57
+ elsif fragment.node_name == "code"
58
+ code fragment[:style], fragment, &recurse
59
+ elsif fragment.node_name == "line"
60
+ line fragment[:style], fragment.text, &recurse
61
+ elsif fragment.node_name == "span"
62
+ span fragment[:style], &recurse
63
+ elsif fragment.node_name == "text"
64
+ text fragment.text, &recurse
65
+ else
66
+ recurse.call
67
+ end
68
+ end
69
+
70
+ def parse
71
+ Nokogiri::HTML.parse(@text)
72
+ end
73
+
74
+ def title(attrs, text, &block)
75
+ @window.to_title_position
76
+ attrib *split_attrs(attrs), &block
77
+ end
78
+
79
+ def body(attrs, fragment, &block)
80
+ vertically_center(fragment)
81
+ attrib *split_attrs(attrs), &block
82
+ end
83
+
84
+ def div(attrs, text, &block)
85
+ @window.setpos(@window.cury + 1, @window.curx)
86
+ horizontally_center(text)
87
+ attrib *split_attrs(attrs), &block
88
+ end
89
+
90
+ def span(attrs, &block)
91
+ attrib *split_attrs(attrs), &block
92
+ end
93
+
94
+ def line(attrs, fragment, &block)
95
+ left_align(fragment)
96
+ attrib *split_attrs(attrs), &block
97
+ end
98
+
99
+ def code(attrs, fragment, &block)
100
+ offset_for_code_box(fragment)
101
+ attrib *split_attrs(attrs), &block
102
+ end
103
+
104
+ def split_attrs(attrs_string)
105
+ attrs_string.to_s.split(",")
106
+ end
107
+
108
+ def text(text)
109
+ @window << text unless text.match(/^\s+$/) # squash white space like real HTML
110
+ yield
111
+ end
112
+
113
+ def attrib *attributes
114
+ attributes.each do |attribute|
115
+ @window.attron Curses.instance_eval(attribute)
116
+ end
117
+ yield
118
+ attributes.each do |attribute|
119
+ @window.attroff Curses.instance_eval(attribute)
120
+ end
121
+ end
122
+
123
+ end
@@ -0,0 +1,55 @@
1
+ require 'curses'
2
+ module Helper::Curses
3
+
4
+ Curses.start_color
5
+ ORANGE_COLOR = 20
6
+ RUBINE_COLOR = 10
7
+ LIGHT_GRAY_COLOR = 91
8
+ DARK_GRAY_COLOR = 92
9
+ Curses.init_color ORANGE_COLOR, 1000, 400, 0
10
+ Curses.init_color RUBINE_COLOR, 800, 0, 250
11
+ Curses.init_color LIGHT_GRAY_COLOR, 500, 500, 500
12
+ Curses.init_color DARK_GRAY_COLOR, 250, 250, 250
13
+
14
+ BLUE = 30
15
+ RED = 70
16
+ CYAN = 40
17
+ GREEN = 50
18
+ MAGENTA = 60
19
+ YELLOW = 80
20
+ WHITE = 90
21
+ ORANGE = 20
22
+ RUBINE = 10
23
+ LIGHT_GRAY = 91
24
+ DARK_GRAY = 92
25
+
26
+ Curses.init_pair BLUE, Curses::COLOR_BLUE, Curses::COLOR_BLACK
27
+ Curses.init_pair RED, Curses::COLOR_RED, Curses::COLOR_BLACK
28
+ Curses.init_pair CYAN, Curses::COLOR_CYAN, Curses::COLOR_BLACK
29
+ Curses.init_pair GREEN, Curses::COLOR_GREEN, Curses::COLOR_BLACK
30
+ Curses.init_pair MAGENTA, Curses::COLOR_MAGENTA, Curses::COLOR_BLACK
31
+ Curses.init_pair YELLOW, Curses::COLOR_YELLOW, Curses::COLOR_BLACK
32
+ Curses.init_pair WHITE, Curses::COLOR_WHITE, Curses::COLOR_BLACK
33
+ Curses.init_pair ORANGE, ORANGE_COLOR, Curses::COLOR_BLACK
34
+ Curses.init_pair RUBINE, RUBINE_COLOR, Curses::COLOR_BLACK
35
+ Curses.init_pair LIGHT_GRAY, LIGHT_GRAY_COLOR, Curses::COLOR_BLACK
36
+ Curses.init_pair DARK_GRAY, DARK_GRAY_COLOR, Curses::COLOR_BLACK
37
+
38
+ def attrib *attributes
39
+ attributes.each do |attribute|
40
+ Curses.attron attribute
41
+ end
42
+ yield
43
+ attributes.each do |attribute|
44
+ Curses.attroff attribute
45
+ end
46
+ end
47
+
48
+ def v_center_text text
49
+ Curses.lines / 2
50
+ end
51
+
52
+ def h_center_text text
53
+ (Curses.cols / 2) - (text.length / 2)
54
+ end
55
+ end
@@ -0,0 +1,4 @@
1
+ module Helper
2
+ end
3
+
4
+ require 'helpers/curses'
@@ -0,0 +1,19 @@
1
+ require 'delegate'
2
+
3
+ class MainWindow < SimpleDelegator
4
+ def initialize window
5
+ super
6
+ refresh
7
+ end
8
+
9
+ def to_title_position
10
+ setpos(1, 1)
11
+ end
12
+
13
+ def refresh
14
+ box("|", "-")
15
+ setpos(0, 0)
16
+ addstr('[ Ruby Fail ]')
17
+ super
18
+ end
19
+ end
data/lib/options.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+ module Options
4
+ def self.parse!
5
+ options = OpenStruct.new host: "localhost", port: 3000,
6
+ start_at: 0
7
+
8
+ OptionParser.new do |opts|
9
+ opts.on("-h", "--host_name=val", String) { |arg| options.host = arg }
10
+ opts.on("-p", "--port=val", Integer) { |arg| options.port = arg }
11
+ opts.on("-s", "--start_at=", Integer) { |arg| options.start_at = arg }
12
+ opts.on("", "--help") { exec "more #{__FILE__}" }
13
+ end.parse!
14
+ options
15
+ end
16
+ end
@@ -0,0 +1,36 @@
1
+ require 'socket'
2
+
3
+ class QandAClient
4
+ def initialize q_and_a_window, host="localhost", port=3000
5
+ @q_and_a_window = q_and_a_window
6
+ @host = host || "localhost"
7
+ @port = port.zero? ? 3000 : port
8
+ @socket = connect
9
+ @q_and_a_window.scrollok true
10
+ listen if connected?
11
+ end
12
+
13
+ def connect
14
+ TCPSocket.new @host, @port
15
+ rescue
16
+ nil
17
+ end
18
+
19
+ def connected?
20
+ !@socket.nil?
21
+ end
22
+
23
+ private
24
+
25
+ def listen
26
+ Thread.new {
27
+ loop do
28
+ @q_and_a_window.setpos 0, 1
29
+ @q_and_a_window << "> "
30
+ @q_and_a_window << @socket.readline
31
+ @q_and_a_window.scrl -1
32
+ @q_and_a_window.refresh
33
+ end
34
+ }
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require 'delegate'
2
+
3
+ class QandAWindow < SimpleDelegator
4
+ def initialize window
5
+ super
6
+ refresh
7
+ end
8
+
9
+ def refresh
10
+ box("|", "-")
11
+ setpos(0, 0)
12
+ addstr('[ Q and A ]')
13
+ super
14
+ end
15
+ end
data/lib/slide_list.rb ADDED
@@ -0,0 +1,19 @@
1
+ class SlideList
2
+ attr_reader :current
3
+
4
+ def initialize(start = 0)
5
+ @current = start - 1
6
+ end
7
+
8
+ def next
9
+ @current += 1
10
+ @current = SLIDES.size - 1 if @current >= SLIDES.size
11
+ BASE_PATH + "#{SLIDES[@current]}.cml"
12
+ end
13
+
14
+ def previous
15
+ @current -= 1
16
+ @current = 0 if @current < 0
17
+ BASE_PATH + "#{SLIDES[@current]}.cml"
18
+ end
19
+ end
@@ -0,0 +1,3 @@
1
+ module TtySlides
2
+ VERSION = "0.0.1"
3
+ end
data/lib/tty_slides.rb ADDED
@@ -0,0 +1,51 @@
1
+ # lazy options parsing:
2
+ # ARGV[0] - the slide you want to start on
3
+ # ARGV[1] - the q and q server hostname
4
+ # ARGV[2] - the q and q server port
5
+ require 'curses'
6
+ require 'io/console'
7
+
8
+ ROOT = File.dirname(__FILE__)
9
+
10
+ require 'tty_slides/version'
11
+ require 'helpers/helper'
12
+ require 'cml/cml'
13
+ require 'main_window'
14
+ require 'q_and_a_window'
15
+ require 'q_and_a_client'
16
+ require 'slide_list'
17
+ require 'options'
18
+
19
+ options = Options.parse!
20
+
21
+ Curses.init_screen
22
+ Curses.noecho
23
+ Curses.curs_set(0) # no cursor please
24
+
25
+ height = Curses.lines
26
+ width = Curses.cols
27
+ main_window = MainWindow.new(Curses::Window.new height - 5, width, 0, 0)
28
+ q_and_a = QandAWindow.new(Curses::Window.new 5, width, main_window.maxy, 0)
29
+ slides = SlideList.new(options.start_at)
30
+
31
+ q_a_client = QandAClient.new(q_and_a, options.host, options.port)
32
+ if !q_a_client.connected?
33
+ main_window.resize height, width
34
+ Curses.refresh
35
+ end
36
+
37
+ CML.new(main_window, File.read(slides.next)).render
38
+
39
+ at_exit { puts "Last Slide: #{slides.current}" if $!.kind_of?(StandardError) }
40
+ at_exit { Curses.close_screen }
41
+
42
+ loop do
43
+ case STDIN.getch
44
+ when /b/i
45
+ CML.new(main_window, File.read(slides.previous)).render
46
+ when /q/i
47
+ exit
48
+ else
49
+ CML.new(main_window, File.read(slides.next)).render
50
+ end
51
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tty_slides/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "tty_slides"
8
+ gem.version = TtySlides::VERSION
9
+ gem.authors = ["Mark Lorenz"]
10
+ gem.email = ["markjlorenz@dapplebeforedawn.com"]
11
+ gem.description = %q{Do a presentation from the terminal.}
12
+ gem.summary = %q{You provides the slides, we'll do the rest.}
13
+ gem.homepage = "https://github.com/dapplebeforedawn/tty_slides"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
17
+ gem.require_paths = ["lib"]
18
+
19
+ gem.add_development_dependency "pry"
20
+
21
+ gem.add_dependency "nokogiri"
22
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tty_slides
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Lorenz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-02-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: pry
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: nokogiri
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Do a presentation from the terminal.
47
+ email:
48
+ - markjlorenz@dapplebeforedawn.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - lib/cml/cml.rb
59
+ - lib/helpers/curses.rb
60
+ - lib/helpers/helper.rb
61
+ - lib/main_window.rb
62
+ - lib/options.rb
63
+ - lib/q_and_a_client.rb
64
+ - lib/q_and_a_window.rb
65
+ - lib/slide_list.rb
66
+ - lib/tty_slides.rb
67
+ - lib/tty_slides/version.rb
68
+ - tty_slides.gemspec
69
+ homepage: https://github.com/dapplebeforedawn/tty_slides
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.24
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: You provides the slides, we'll do the rest.
93
+ test_files: []