tty_slides 0.0.1

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.
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: []