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