mars_base_10 0.3.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
+ SHA256:
3
+ metadata.gz: d66886ffab2da6f395505f68090aa88ea77f7e9bb17669c79a562d9298338227
4
+ data.tar.gz: '084f5515249cad9b00649ccf84743e848e835cc3b234f95608efb6bcd5bf5ab7'
5
+ SHA512:
6
+ metadata.gz: 8c4284d80f4071dfde88bef0f7cf1dfa2f684675a8861b13527740f891bf67bb59e48a6cc95afe627425d1017cd6f355ba8075def3e144ac0066287d90c84ab3
7
+ data.tar.gz: 46fdbcd3ddae7f293f21bd067c042555ce235daef318f4431edc2d30ea05ce92f0783bf96c7103455f3c41f15f1c2f2f94b8dc5382780d7a6ea4f2fb89e6e052
data/bin/mb10 ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # require "bundler/setup"
5
+ # require "mars_base_10/comm_central"
6
+
7
+ # begin
8
+ # cc = MarsBase10::CommCentral.new
9
+ # cc.activate
10
+ # ensure
11
+ # cc.shutdown
12
+ # end
13
+
14
+ lib_path = File.expand_path("../lib", __dir__)
15
+ $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path)
16
+ require "mars_base_10/cli"
17
+
18
+ Signal.trap("INT") do
19
+ warn("\n#{caller.join("\n")}: interrupted")
20
+ exit(1)
21
+ end
22
+
23
+ begin
24
+ MarsBase10::CLI.start
25
+ rescue MarsBase10::CLI::Error => err
26
+ puts "ERROR: #{err.message}"
27
+ exit 1
28
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MarsBase10
4
+ class ActionBar
5
+ attr_accessor :actions
6
+
7
+ def initialize(actions:)
8
+ @actions = actions
9
+ @viewport = nil
10
+ @win = nil
11
+ end
12
+
13
+ def self.Default
14
+ ActionBar.new actions: {'j': 'Move Down', 'k': 'Move Up', 'q': 'Quit'}
15
+ end
16
+
17
+ def actions_first_col
18
+ (self.width - self.actions_width)/2
19
+ end
20
+
21
+ def actions_width
22
+ self.actions.values.inject(0) {|acc, item| acc += (3 + 2 + item.length + 2)}
23
+ end
24
+
25
+ def add_action(a_hash)
26
+ self.actions = Hash[@actions.merge!(a_hash).sort]
27
+ self
28
+ end
29
+
30
+ def draw
31
+ self.window.attron(Curses.color_pair(2))
32
+ self.window.setpos(0, 0)
33
+ self.window.addstr("Actions:")
34
+ self.window.addstr(" " * (self.actions_first_col - 8))
35
+
36
+ self.actions.each do |key, value|
37
+ self.window.attron(Curses::A_REVERSE)
38
+ self.window.addstr(" #{key} ")
39
+ self.window.attroff(Curses::A_REVERSE) # if item == self.index
40
+ self.window.addstr(" #{value} ")
41
+ end
42
+
43
+ self.window.addstr(" " * (self.width - (self.actions_first_col + self.actions_width)))
44
+ self.window.attroff(Curses.color_pair(2))
45
+ end
46
+
47
+ def display_on(viewport:)
48
+ @viewport = viewport
49
+ end
50
+
51
+ def first_col
52
+ 0
53
+ end
54
+
55
+ def first_row
56
+ @viewport.max_rows
57
+ end
58
+
59
+ def height
60
+ 1
61
+ end
62
+
63
+ def remove_action(key)
64
+ self.actions.delete_if {|k, v| k == key}
65
+ self
66
+ end
67
+
68
+ def width
69
+ @viewport.max_cols
70
+ end
71
+
72
+ def window
73
+ return @win if @win
74
+ @win = Curses::Window.new(self.height, self.width, self.first_row, self.first_col)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "thor"
4
+ require "pastel"
5
+ require "tty-font"
6
+
7
+ module MarsBase10
8
+ # Handle the application command line parsing
9
+ # and the dispatch to various command objects
10
+ #
11
+ # @api public
12
+ class CLI < Thor
13
+ class_option :"no-color", type: :boolean, default: false, desc: "Disable colorization in output"
14
+
15
+ # Error raised by this runner
16
+ Error = Class.new(StandardError)
17
+
18
+ def self.exit_on_failure?
19
+ true
20
+ end
21
+
22
+ desc "help, --help, -h", "Describe available commands or one specific command"
23
+ map %w[-h --help] => :help
24
+ def help(*args)
25
+ font = TTY::Font.new(:standard)
26
+ pastel = Pastel.new(enabled: !options["no-color"])
27
+ puts pastel.yellow(font.write("Mars Base 10"))
28
+ super
29
+ end
30
+
31
+ desc "launch [SHIP_CONFIG]", "Start Mars Base 10 connected to the Urbit ship defined in SHIP_CONFIG. (Required)"
32
+ long_desc <<-DESC
33
+ The SHIP_CONFIG uses the yaml format defined in the ruby urbit-api gem.
34
+ see https://github.com/Zaxonomy/urbit-ruby
35
+ DESC
36
+ method_option :help, aliases: %w[-h --help], type: :boolean, desc: "Display usage information"
37
+ def launch(config)
38
+ if options[:help]
39
+ invoke :help, ["launch"]
40
+ else
41
+ if (config)
42
+ require_relative "comm_central"
43
+ begin
44
+ cc = MarsBase10::CommCentral.new config_filename: config
45
+ cc.activate
46
+ ensure
47
+ cc.shutdown
48
+ end
49
+ else
50
+ raise Error, "A SHIP_CONFIG is required to launch."
51
+ end
52
+ end
53
+ end
54
+
55
+
56
+ desc "version, --version, -v", "print the version number, then exit"
57
+ map %w[-v --version] => :version
58
+ def version
59
+ require_relative "version"
60
+ puts "v#{MarsBase10::VERSION}"
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ require 'urbit/urbit'
3
+
4
+ require_relative 'graph_rover'
5
+ require_relative 'viewport'
6
+
7
+ module MarsBase10
8
+ class Error < StandardError; end
9
+
10
+ class CommCentral
11
+ def initialize(config_filename:)
12
+ @viewport = Viewport.new
13
+ @rover = GraphRover.new ship_connection: Urbit.connect(config_file: config_filename),
14
+ viewport: @viewport
15
+ end
16
+
17
+ def activate
18
+ self.rover.start
19
+ end
20
+
21
+ def shutdown
22
+ self.rover.stop
23
+ end
24
+
25
+ private
26
+
27
+ def rover
28
+ @rover
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ship'
4
+ require_relative 'stack'
5
+ require_relative 'subject'
6
+
7
+ module MarsBase10
8
+ class GraphRover
9
+ attr_reader :panes, :ship, :viewport
10
+
11
+ def initialize(ship_connection:, viewport:)
12
+ @ship = Ship.new connection: ship_connection
13
+ @stack = Stack.new
14
+ @viewport = viewport
15
+ @viewport.controller = self
16
+
17
+ @panes = []
18
+
19
+ # The graph list is a fixed width, variable height (full screen) pane on the left.
20
+ @graph_list_pane = @viewport.add_pane width_pct: 0.3
21
+ @graph_list_pane.viewing subject: @ship.graph_names
22
+
23
+ # The node list is a variable width, fixed height pane in the upper right.
24
+ @node_list_pane = @viewport.add_variable_width_pane at_col: @graph_list_pane.last_col,
25
+ height_pct: 0.5
26
+ @node_list_pane.viewing subject: @ship.node_list
27
+
28
+ # The single node viewer is a variable width, variable height pane in the lower right.
29
+ @node_view_pane = @viewport.add_variable_both_pane at_row: @node_list_pane.last_row,
30
+ at_col: @graph_list_pane.last_col
31
+ @node_view_pane.viewing subject: @ship.node
32
+
33
+ self.viewport.action_bar = ActionBar.Default.add_action({'i': 'Inspect'})
34
+ self.viewport.activate pane: @graph_list_pane
35
+ self.resync
36
+ end
37
+
38
+ #
39
+ # Called by a pane in this controller for bubbling a key press up
40
+ #
41
+ def send(key:)
42
+ case key
43
+ when 'd' # (D)ive
44
+ begin
45
+ if @node_view_pane.subject.contents[4].include?('true')
46
+ self.viewport.action_bar.add_action({'p': 'Pop Out'})
47
+ resource = @graph_list_pane.current_subject
48
+ node_index = @node_list_pane.current_subject
49
+ @stack.push(resource)
50
+ @node_list_pane.clear
51
+ @node_list_pane.subject.contents = self.ship.fetch_node_children resource: resource, index: node_index
52
+ @node_list_pane.index = 0
53
+ end
54
+ end
55
+ when 'i' # (I)nspect
56
+ begin
57
+ self.viewport.activate pane: @node_list_pane
58
+ self.viewport.action_bar = ActionBar.Default.add_action({'d': 'Dive In', 'g': 'Graph List'})
59
+ end
60
+ when 'g' # (G)raph View
61
+ unless @graph_list_pane.active?
62
+ self.viewport.activate pane: @graph_list_pane
63
+ end
64
+ when 'p' # (P)op
65
+ begin
66
+ if (resource = @stack.pop)
67
+ @node_list_pane.clear
68
+ @node_list_pane.subject.contents = self.ship.fetch_node_list(resource: resource)
69
+ @node_list_pane.index = 0
70
+ end
71
+ if (@stack.length == 0)
72
+ self.viewport.action_bar.remove_action(:p)
73
+ end
74
+ end
75
+ end
76
+ self.resync
77
+ end
78
+
79
+ def start
80
+ self.viewport.open
81
+ end
82
+
83
+ def stop
84
+ self.viewport.close
85
+ end
86
+
87
+ private
88
+
89
+ def resync
90
+ self.resync_node_view(self.resync_node_list)
91
+ end
92
+
93
+ def resync_node_list
94
+ resource = @graph_list_pane.current_subject
95
+ if @graph_list_pane == self.viewport.active_pane
96
+ @node_list_pane.subject.title = "Nodes of #{resource}"
97
+ @node_list_pane.clear
98
+ @node_list_pane.subject.first_row = 0
99
+ @node_list_pane.subject.contents = self.ship.fetch_node_list resource: resource
100
+ end
101
+ resource
102
+ end
103
+
104
+ def resync_node_view(resource)
105
+ node_index = @node_list_pane.current_subject
106
+ @node_view_pane.subject.title = "Node #{self.short_index node_index}"
107
+ @node_view_pane.clear
108
+ @node_view_pane.subject.contents = self.ship.fetch_node_contents(resource: resource, index: node_index)
109
+ end
110
+
111
+ def short_index(index)
112
+ return "" if index.nil?
113
+ tokens = index.split('.')
114
+ "#{tokens[0]}..#{tokens[tokens.size - 2]}.#{tokens[tokens.size - 1]}"
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+ require 'curses'
3
+
4
+ module MarsBase10
5
+ class Pane
6
+ attr_accessor :draw_row, :draw_col, :index, :latch, :subject
7
+ attr_reader :height_pct, :left_edge_col, :top_row, :viewport, :width_pct
8
+
9
+ def initialize(viewport:, at_row:, at_col:, height_pct: 1, width_pct: 1)
10
+ @top_row = at_row
11
+ @left_edge_col = at_col
12
+ @height_pct = height_pct
13
+ @index = 0
14
+ @latch = -1
15
+ @subject = nil
16
+ @win = nil
17
+ @viewport = viewport
18
+ @width_pct = width_pct
19
+ end
20
+
21
+ def active?
22
+ self == self.viewport.active_pane
23
+ end
24
+
25
+ def clear
26
+ self.prepare_for_writing_contents
27
+ (0..(self.last_row - 1)).each do |item|
28
+ self.window.setpos(self.draw_row, self.draw_col)
29
+ self.window.addstr("")
30
+ self.window.clrtoeol
31
+ self.draw_row += 1
32
+ end
33
+ end
34
+
35
+ def current_subject
36
+ self.subject.at index: self.index
37
+ end
38
+
39
+ def draw
40
+ self.prepare_for_writing_contents
41
+
42
+ (0..(self.max_contents_rows - 1)).each do |item|
43
+ self.window.setpos(self.draw_row, self.draw_col)
44
+ # The string here is the gutter followed by the window contents. improving the gutter is tbd.
45
+ self.window.attron(Curses::A_REVERSE) if item == self.index
46
+ self.window.addstr("#{"%02d" % item} #{self.subject.at index: item}")
47
+ self.window.attroff(Curses::A_REVERSE) # if item == self.index
48
+ self.window.clrtoeol
49
+ self.draw_row += 1
50
+ end
51
+
52
+ self.draw_border
53
+ end
54
+
55
+ def draw_border
56
+ self.window.attron(Curses.color_pair(1) | Curses::A_BOLD) if self.active?
57
+ self.window.box
58
+ self.draw_title
59
+ self.window.attroff(Curses.color_pair(1) | Curses::A_BOLD) if self.active?
60
+ end
61
+
62
+ def draw_title
63
+ self.window.setpos(0, 2)
64
+ self.window.addstr(" #{self.subject.title} (#{self.subject.rows} total) ")
65
+ end
66
+
67
+ def first_col
68
+ 1
69
+ end
70
+
71
+ def first_row
72
+ 1
73
+ end
74
+
75
+ def gutter_width
76
+ 4
77
+ end
78
+
79
+ #
80
+ # This is the _relative_ last column, e.g. the width of the pane in columns.
81
+ #
82
+ def last_col
83
+ [(self.viewport.max_cols * self.width_pct).floor, self.min_column_width].max
84
+ end
85
+
86
+ #
87
+ # This is the _relative_ last row, e.g. the height of the pane in columns.
88
+ #
89
+ def last_row
90
+ (self.viewport.max_rows * self.height_pct).floor
91
+ end
92
+
93
+ #
94
+ # The pane is latched if it has consumed 1 key 0-9 and is awaiting the next key.
95
+ #
96
+ def latched?
97
+ self.latch != -1
98
+ end
99
+
100
+ def max_contents_rows
101
+ self.subject.rows
102
+ end
103
+
104
+ def min_column_width
105
+ self.gutter_width + self.subject.cols + self.right_pad
106
+ end
107
+
108
+ def prepare_for_writing_contents
109
+ self.draw_row = self.first_row
110
+ self.draw_col = self.first_col
111
+ end
112
+
113
+ #
114
+ # process blocks and waits for a keypress.
115
+ #
116
+ # this method handles only the "default" keypresses which all controllers/subjects
117
+ # must support. Any unrecognized key is bubbled to the controller for more specific
118
+ # handling.
119
+ #
120
+ def process
121
+ key = self.window.getch.to_s
122
+ case key
123
+ when 'j'
124
+ self.set_row(self.index + 1)
125
+ when 'k'
126
+ self.set_row(self.index - 1)
127
+ when 'q'
128
+ exit 0
129
+ when ('0'..'9')
130
+ if self.latched?
131
+ self.set_row((self.latch * 10) + key.to_i)
132
+ self.latch = -1
133
+ else
134
+ self.latch = key.to_i
135
+ end
136
+ end
137
+
138
+ # Always send the key to the controller for additional processing...
139
+ self.viewport.controller.send key: key
140
+ end
141
+
142
+ def right_pad
143
+ 2
144
+ end
145
+
146
+ #
147
+ # this is a no-op if the index is out of range
148
+ #
149
+ def set_row(i)
150
+ self.subject.scroll_limit = [self.last_row - 1, self.max_contents_rows].min
151
+
152
+ if (i < 0)
153
+ self.subject.scroll_up
154
+ i = 0
155
+ end
156
+
157
+ # If we've reached the end of the content, it's a no-op.
158
+ if (i >= self.max_contents_rows)
159
+ i -= 1
160
+ end
161
+
162
+ if (i >= self.last_row - 2)
163
+ self.subject.scroll_down
164
+ i -= 1
165
+ end
166
+
167
+ self.index = i # if (i <= self.max_contents_rows) && (i >= 0)
168
+ end
169
+
170
+ def viewing(subject:)
171
+ @subject = subject
172
+ end
173
+
174
+ def window
175
+ return @win if @win
176
+ @win = Curses::Window.new(self.last_row, self.last_col, self.top_row, self.left_edge_col)
177
+ end
178
+ end
179
+
180
+ class VariableBothPane < Pane
181
+ def last_col
182
+ self.viewport.max_cols - self.left_edge_col
183
+ end
184
+
185
+ def last_row
186
+ self.viewport.max_rows - self.top_row
187
+ end
188
+ end
189
+
190
+ class VariableWidthPane < Pane
191
+ def last_col
192
+ self.viewport.max_cols - self.left_edge_col
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'subject'
4
+
5
+ module MarsBase10
6
+ class Ship
7
+ def initialize(connection:)
8
+ @ship = connection
9
+ end
10
+
11
+ def graph_names
12
+ Subject.new title: 'Graphs', contents: @ship.graph_names
13
+ end
14
+
15
+ def node
16
+ Subject.new title: 'Node', contents: []
17
+ end
18
+
19
+ def node_list
20
+ Subject.new title: 'Node List', contents: []
21
+ end
22
+
23
+ def fetch_node(resource:, index:)
24
+ @ship.graph(resource: resource).node(index: index)
25
+ end
26
+
27
+ def fetch_node_children(resource:, index:)
28
+ self.fetch_node(resource: resource, index: index).children.map {|node| node.index}.sort
29
+ end
30
+
31
+ def fetch_node_contents(resource:, index:)
32
+ return [] unless (n = self.fetch_node(resource: resource, index: index))
33
+ n.to_pretty_array
34
+ end
35
+
36
+ def fetch_node_list(resource:)
37
+ @ship.graph(resource: resource).newest_nodes(count: 20).map {|node| node.index}.sort
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # Original code by Ale Miralles
4
+ # https://gist.github.com/amiralles/197b4ed1e7034d0e3f79b92ec76a5a80
5
+ #
6
+ # pop() wasn't implemented correctly, though. fixed that.
7
+ #
8
+ module MarsBase10
9
+ class Stack
10
+ class Node
11
+ attr_accessor :next, :data
12
+ def initialize data
13
+ self.data = data
14
+ self.next = nil
15
+ end
16
+ end
17
+
18
+ attr_accessor :head, :tail, :length
19
+
20
+ # Initialize an empty stack.
21
+ # Complexity: O(1).
22
+ def initialize
23
+ self.head = nil
24
+ self.tail = nil
25
+ self.length = 0
26
+ end
27
+
28
+ # Pops all elements from the stack.
29
+ # Complexity O(n).
30
+ def clear
31
+ while peek
32
+ pop
33
+ end
34
+ end
35
+
36
+ # Enumerator (common ruby idiom).
37
+ # Loops over the stack (from head to tail) yielding one item at a time.
38
+ # Complexity: yield next element is O(1),
39
+ # yield all elements is O(n).
40
+ def each
41
+ return nil unless block_given?
42
+
43
+ current = self.head
44
+ while current
45
+ yield current
46
+ current = current.next
47
+ end
48
+ end
49
+
50
+ # Returns the element that's at the top of the stack without removing it.
51
+ # Complexity O(1).
52
+ def peek
53
+ self.head
54
+ end
55
+
56
+ # Prints the contents of the stack.
57
+ # Complexity: O(n).
58
+ def print
59
+ if self.length == 0
60
+ puts "empty"
61
+ else
62
+ self.each { |node| puts node.data }
63
+ end
64
+ end
65
+
66
+ # Removes the element that's at the top of the stack.
67
+ # Complexity O(1).
68
+ def pop
69
+ return nil unless self.length > 0
70
+ n = self.head
71
+ self.head = self.head.next
72
+ self.tail = nil if self.length == 1
73
+ self.length -= 1
74
+ n.data
75
+ end
76
+
77
+ # Inserts a new element into the stack.
78
+ # Complexity O(1).
79
+ def push data
80
+ node = Node.new data
81
+ if length == 0
82
+ self.tail = node
83
+ end
84
+
85
+ node.next = self.head
86
+ self.head = node
87
+ self.length += 1
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MarsBase10
4
+ class Subject
5
+ attr_accessor :first_row, :scroll_limit, :title
6
+
7
+ def initialize(title: 'Untitled', contents:)
8
+ @contents = contents
9
+ @first_row = 0
10
+ @title = title
11
+ end
12
+
13
+ # Returns the item at: the index: relative to the first_row.
14
+ def at(index:)
15
+ self.contents[self.first_row + index]
16
+ end
17
+
18
+ def cols
19
+ return @cols if @cols
20
+ @cols = @contents.inject(0) {|a, n| n.length > a ? n.length : a}
21
+ end
22
+
23
+ def contents
24
+ @contents
25
+ end
26
+
27
+ def contents=(a_contents_array)
28
+ @rows = nil
29
+ $cols = nil
30
+ @contents = a_contents_array
31
+ end
32
+
33
+ def rows
34
+ return @rows if @rows
35
+ @rows = @contents.size
36
+ end
37
+
38
+ def scroll_down
39
+ self.first_row = [self.first_row + 1, (self.rows - self.scroll_limit)].min
40
+ end
41
+
42
+ def scroll_up
43
+ self.first_row = [self.first_row - 1, 0].max
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MarsBase10
4
+ VERSION = "0.3.0"
5
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+ require 'curses'
3
+
4
+ require_relative 'action_bar'
5
+ require_relative 'pane'
6
+
7
+ module MarsBase10
8
+ class Viewport
9
+ attr_accessor :controller
10
+ attr_reader :panes, :win
11
+
12
+ CURSOR_INVISIBLE = 0
13
+ CURSOR_VISIBLE = 1
14
+
15
+ def initialize
16
+ Curses.init_screen
17
+ Curses.curs_set(CURSOR_INVISIBLE)
18
+ Curses.noecho # Do not echo characters typed by the user.
19
+
20
+ Curses.start_color if Curses.has_colors?
21
+ Curses.init_pair(1, Curses::COLOR_RED, Curses::COLOR_BLACK)
22
+ Curses.init_pair(2, Curses::COLOR_BLACK, Curses::COLOR_CYAN)
23
+
24
+ @active_pane = nil
25
+ @controller = nil
26
+
27
+ @action_bar = nil
28
+ @panes = []
29
+
30
+ # this is the whole visible drawing surface.
31
+ # we don't ever draw on this, but we need it for reference.
32
+ @win = Curses::Window.new 0, 0, 0, 0
33
+ end
34
+
35
+ def action_bar
36
+ return @action_bar unless @action_bar.nil?
37
+ # Make a default action bar. Only movement for now.
38
+ self.action_bar = ActionBar.new actions: {'j': 'Move Down', 'k': 'Move Up', 'q': 'Quit'}
39
+ end
40
+
41
+ def action_bar=(an_action_bar)
42
+ @action_bar = an_action_bar
43
+ @action_bar.display_on viewport: self
44
+ @action_bar
45
+ end
46
+
47
+ def activate(pane:)
48
+ @active_pane = pane
49
+ end
50
+
51
+ #
52
+ # This is the pane in the Viewport which is actively accepting keyboard input.
53
+ #
54
+ def active_pane
55
+ @active_pane
56
+ end
57
+
58
+ #
59
+ # Adds a new drawable area (Pane) to the viewport.
60
+ # By default it is anchored to the top left. (min_row, min_col)
61
+ # and full screen. (height and width 100%)
62
+ #
63
+ def add_pane(at_row: self.min_row, at_col: self.min_col, height_pct: 1, width_pct: 1)
64
+ p = MarsBase10::Pane.new viewport: self,
65
+ at_row: at_row,
66
+ at_col: at_col,
67
+ height_pct: height_pct,
68
+ width_pct: width_pct
69
+ @panes << p
70
+ @active_pane = p
71
+ end
72
+
73
+ def add_variable_width_pane(at_row: self.min_row, at_col: self.min_col, height_pct:)
74
+ p = VariableWidthPane.new viewport: self,
75
+ at_row: at_row,
76
+ at_col: at_col,
77
+ height_pct: height_pct
78
+ @panes << p
79
+ p
80
+ end
81
+
82
+ #
83
+ # Adds a new variable width drawable area (VariableBothPane) to the
84
+ # right-hand side of the viewport.
85
+ #
86
+ # The caller must specify the upper left corner (at_row, at_col) but
87
+ # after that it will automatically adjust its width based upon how
88
+ # many columns the left pane(s) use.
89
+ #
90
+ def add_variable_both_pane(at_row: self.min_row, at_col: self.min_col)
91
+ p = VariableBothPane.new viewport: self,
92
+ at_row: at_row,
93
+ at_col: at_col
94
+ @panes << p
95
+ p
96
+ end
97
+
98
+ def close
99
+ Curses.close_screen
100
+ end
101
+
102
+ def max_cols
103
+ self.win.maxx
104
+ end
105
+
106
+ def max_rows
107
+ self.win.maxy - 1
108
+ end
109
+
110
+ def min_col
111
+ 0
112
+ end
113
+
114
+ def min_row
115
+ 0
116
+ end
117
+
118
+ def open
119
+ loop do
120
+ self.panes.each do |pane|
121
+ pane.draw
122
+ pane.window.refresh
123
+ end
124
+ self.action_bar.draw
125
+ self.action_bar.window.refresh
126
+ self.active_pane.process
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,4 @@
1
+ require "mars_base_10/version"
2
+
3
+ module MarsBase10
4
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/mars_base_10/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "mars_base_10"
7
+ spec.version = MarsBase10::VERSION
8
+ spec.license = "MIT"
9
+ spec.authors = ["Daryl Richter"]
10
+ spec.email = ["daryl@ngzax.com"]
11
+ spec.homepage = "https://www.ngzax.com"
12
+ spec.summary = "This is the urbit console you have been waiting for"
13
+ spec.description = "A keyboard maximalist, curses-based, urbit terminal ui. It uses the (also in development) ruby airlock."
14
+
15
+ if spec.respond_to?(:metadata=)
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/Zaxonomy/mars-base-10"
18
+ spec.metadata["changelog_uri"] = "https://github.com/Zaxonomy/mars-base-10/CHANGELOG.md"
19
+ end
20
+
21
+ spec.required_ruby_version = ">= 2.7.0"
22
+
23
+ spec.files = Dir.glob("lib{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
24
+ spec.files += %w[mars_base_10.gemspec] # include the gemspec itself because warbler breaks w/o it
25
+
26
+ spec.bindir = "bin"
27
+ spec.executables = %w[mb10]
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "curses", "~> 1.4"
31
+ spec.add_dependency "pastel", "~> 0.8"
32
+ spec.add_dependency "sorted_set", "~> 1.0"
33
+ spec.add_dependency "thor", "~> 1.1"
34
+ spec.add_dependency "tty-font", "~> 0.5"
35
+ spec.add_dependency "urbit-api", "~> 0.2.1"
36
+
37
+ spec.add_development_dependency "pry", "~> 0.13"
38
+ spec.add_development_dependency "rspec", "~> 3.10"
39
+ spec.add_development_dependency "rubocop", "~> 1.7"
40
+ end
metadata ADDED
@@ -0,0 +1,187 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mars_base_10
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Daryl Richter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-11-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: curses
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pastel
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.8'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.8'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sorted_set
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tty-font
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: urbit-api
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.2.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.2.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.13'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.13'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.10'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.10'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.7'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.7'
139
+ description: A keyboard maximalist, curses-based, urbit terminal ui. It uses the (also
140
+ in development) ruby airlock.
141
+ email:
142
+ - daryl@ngzax.com
143
+ executables:
144
+ - mb10
145
+ extensions: []
146
+ extra_rdoc_files: []
147
+ files:
148
+ - bin/mb10
149
+ - lib/mars_base_10.rb
150
+ - lib/mars_base_10/action_bar.rb
151
+ - lib/mars_base_10/cli.rb
152
+ - lib/mars_base_10/comm_central.rb
153
+ - lib/mars_base_10/graph_rover.rb
154
+ - lib/mars_base_10/pane.rb
155
+ - lib/mars_base_10/ship.rb
156
+ - lib/mars_base_10/stack.rb
157
+ - lib/mars_base_10/subject.rb
158
+ - lib/mars_base_10/version.rb
159
+ - lib/mars_base_10/viewport.rb
160
+ - mars_base_10.gemspec
161
+ homepage: https://www.ngzax.com
162
+ licenses:
163
+ - MIT
164
+ metadata:
165
+ homepage_uri: https://www.ngzax.com
166
+ source_code_uri: https://github.com/Zaxonomy/mars-base-10
167
+ changelog_uri: https://github.com/Zaxonomy/mars-base-10/CHANGELOG.md
168
+ post_install_message:
169
+ rdoc_options: []
170
+ require_paths:
171
+ - lib
172
+ required_ruby_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: 2.7.0
177
+ required_rubygems_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ requirements: []
183
+ rubygems_version: 3.1.6
184
+ signing_key:
185
+ specification_version: 4
186
+ summary: This is the urbit console you have been waiting for
187
+ test_files: []