tweemux 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemspec +16 -0
- data/.gitignore +2 -0
- data/Gemfile +4 -0
- data/Guardfile +1 -0
- data/LICENSE.txt +1 -0
- data/README.rdoc +49 -0
- data/Rakefile +1 -0
- data/bin/tweemux +3 -0
- data/lib/tweemux.rb +21 -0
- data/lib/tweemux/action.rb +87 -0
- data/lib/tweemux/action/at.rb +8 -0
- data/lib/tweemux/action/bro.rb +3 -0
- data/lib/tweemux/action/forward.rb +33 -0
- data/lib/tweemux/action/host.rb +7 -0
- data/lib/tweemux/action/share.rb +7 -0
- data/lib/tweemux/action/sis.rb +2 -0
- data/lib/tweemux/core_ext.rb +30 -0
- data/lib/tweemux/version.rb +3 -0
- data/test/action_test_helper.rb +25 -0
- data/test/test_helper.rb +9 -0
- data/test/tweemux/action/at_test.rb +16 -0
- data/test/tweemux/action/forward_test.rb +17 -0
- data/test/tweemux/action/host_test.rb +11 -0
- data/test/tweemux/action/share_test.rb +11 -0
- data/test/tweemux/action_test.rb +10 -0
- data/test/tweemux_test.rb +27 -0
- metadata +88 -0
data/.gemspec
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'working/gemspec'
|
3
|
+
$:.unshift './lib'
|
4
|
+
require 'tweemux/version'
|
5
|
+
|
6
|
+
Working.gemspec(
|
7
|
+
:name => 'tweemux',
|
8
|
+
:summary => Working.third_line_of_readme,
|
9
|
+
#:description => Working.readme_snippet(/== Usage/, /== TODO/),
|
10
|
+
:description => Working.third_line_of_readme,
|
11
|
+
:version => Tweemux::VERSION,
|
12
|
+
:authors => %w(☈king),
|
13
|
+
:email => 'rking-tweemux@sharpsaw.org',
|
14
|
+
:github => 'rking/tweemux',
|
15
|
+
:deps => %w(),
|
16
|
+
)
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'working/guard'
|
data/LICENSE.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Copyleft (ↄ) 2013 ☈king — CC0/Public Domain.
|
data/README.rdoc
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
= tweemux
|
2
|
+
|
3
|
+
For Remote Pair Programming: A handy script to create-or-join a world-readable tmux session (smaller than wemux)
|
4
|
+
|
5
|
+
One of the aims of this script is to be convenient. Another of its aims is to be transparent (read: loud) about what it's doing. None of the parts of this are inherently complicated, and any of them is prone to need additional debugging, so it's best that `tweemux`'s behavior is visible.
|
6
|
+
|
7
|
+
== Problem
|
8
|
+
|
9
|
+
Though tmux is an amazing tool, some of the parts of it are low-level. As you can see from the tmux portion of this script's source, there isn't much you have to do to get a shared session, but it's still more than you want you or your pair to think about when you're trying to work. The `wemux` script is similar to this, but I don't care for its complexity/optionality/config (and the thing that really bothers me is that it has a 'read-only' mode that only is read-only because of the command used to connect to the socket — the socket itself is still `chmod 777`, so you might as well be honest about the idea that the session could be connected by any user in writeable mode).
|
10
|
+
|
11
|
+
Once you've solved the "shared tmux" problem, that's actually the easier part of it. This script goes a little further and helps with the user and SSH connection problems.
|
12
|
+
|
13
|
+
== Host Usage
|
14
|
+
|
15
|
+
For starters:
|
16
|
+
|
17
|
+
gem install tweemux
|
18
|
+
|
19
|
+
Then, create the user on your machine:
|
20
|
+
|
21
|
+
tweemux bro lwoodson # when their Github username == desired Unix username
|
22
|
+
# -or-
|
23
|
+
tweemux bro cirwin github: ConradIrwin
|
24
|
+
# -or-
|
25
|
+
tweemux sis ghopper # synonym for 'bro'
|
26
|
+
|
27
|
+
If you're on a machine behind a firewall, use one that is *not* behind a firewall that you also have SSH access to (in this example, sharpsaw.org is the one not behind the firewall):
|
28
|
+
|
29
|
+
tweemux forward local 22 from sharpsaw.org 3322
|
30
|
+
# ^ Or, if you're in control of the router, you can just open a port and
|
31
|
+
# point your pair at your actual IP (`curl ifconfig.me` comes in handy for
|
32
|
+
# finding the public IP)
|
33
|
+
|
34
|
+
Then finally:
|
35
|
+
|
36
|
+
tweemux host
|
37
|
+
|
38
|
+
== Guest Usage
|
39
|
+
|
40
|
+
gem install tweemux
|
41
|
+
tweemux at sharpsaw.org 3322 # uses the 'forward' set up from above
|
42
|
+
|
43
|
+
== Going Further
|
44
|
+
|
45
|
+
It's also nice to share a windowing environment session as well. For example, the "Guest" can host a VNC that you, as the tweemux host, can then connect to. This allows you to "point" at things with the mouse, and to share web browsing, etc.
|
46
|
+
|
47
|
+
== No Public Box?
|
48
|
+
|
49
|
+
If you don't have a public SSH account (like the way I use sharpsaw.org, above), let me know ( i-am-stuck@sharpsaw.org ). We'll solve that.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'working/rake_tasks'
|
data/bin/tweemux
ADDED
data/lib/tweemux.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'tweemux/action'
|
2
|
+
require 'tweemux/core_ext'
|
3
|
+
|
4
|
+
class Tweemux
|
5
|
+
SOCK = '/tmp/tweemux.sock'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def run args
|
9
|
+
action = understand args
|
10
|
+
action.call
|
11
|
+
end
|
12
|
+
|
13
|
+
def understand args
|
14
|
+
action = args.shift
|
15
|
+
klass = Tweemux::Action.const_get action.capitalize
|
16
|
+
klass.new args
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Tweemux
|
4
|
+
class Action
|
5
|
+
def initialize args; @args = args end
|
6
|
+
|
7
|
+
def call
|
8
|
+
run @args
|
9
|
+
end
|
10
|
+
|
11
|
+
class FunkyUsage < RuntimeError; end
|
12
|
+
|
13
|
+
def run args; raise 'Unimplemented' end
|
14
|
+
|
15
|
+
# Hrm. This is kinda gross, but I feel like it cleans up the subclasses
|
16
|
+
def explained_run *a; self.class.explained_run *a end
|
17
|
+
def tmux_S *a; self.class.tmux_S *a end
|
18
|
+
|
19
|
+
class DubiousSystemInvocation < RuntimeError; end
|
20
|
+
class << self
|
21
|
+
def tmux_S args, why
|
22
|
+
cmd = %W(tmux -S #{SOCK}) + args
|
23
|
+
explained_run cmd, why
|
24
|
+
end
|
25
|
+
|
26
|
+
def explained_run what, why
|
27
|
+
raise DubiousSystemInvocation, "Given string-arg of #{what}" \
|
28
|
+
if what.is_a? String
|
29
|
+
explain what, why
|
30
|
+
system_or_raise what
|
31
|
+
end
|
32
|
+
|
33
|
+
def explain what, why
|
34
|
+
warn highlight_command(what) + highlight_explanation(why)
|
35
|
+
end
|
36
|
+
|
37
|
+
def system_or_raise cmd
|
38
|
+
system *cmd or pseudo_restarts(*cmd)
|
39
|
+
end
|
40
|
+
|
41
|
+
def highlight_command arr
|
42
|
+
':Running'.color(:middle_blue) \
|
43
|
+
+ '; '.color(:gray245) \
|
44
|
+
+ colorize_tmux_command(arr)
|
45
|
+
end
|
46
|
+
|
47
|
+
def highlight_explanation msg
|
48
|
+
' # '.color(:orange) + msg.color(:lemon)
|
49
|
+
end
|
50
|
+
|
51
|
+
def colorize_tmux_command arr
|
52
|
+
# Index access in Ruby is a smell. @ConradIrwin, help me!!
|
53
|
+
socket_idx = arr.find_index '-S'
|
54
|
+
arr.inject [] do |a,e|
|
55
|
+
a <<
|
56
|
+
if socket_idx and (e == arr[socket_idx] or e == arr[socket_idx+1])
|
57
|
+
e.color :gray245
|
58
|
+
else
|
59
|
+
e.color :brighty_blue
|
60
|
+
end
|
61
|
+
end.join ' '
|
62
|
+
end
|
63
|
+
|
64
|
+
def pseudo_restarts cmd
|
65
|
+
warn '# failed ☹'.color :error
|
66
|
+
ctrl_c = 'Ctrl+c'.color :keypress, :prompt
|
67
|
+
enter = 'Enter'.color :keypress, :prompt
|
68
|
+
# TODO: work pry-rescue into this so we can offer a 'try-again'
|
69
|
+
# See also: https://github.com/ConradIrwin/pry-rescue/issues/29
|
70
|
+
warn <<-EOT.color :prompt
|
71
|
+
To give up, hit: #{ctrl_c}
|
72
|
+
To run anyway, hit: #{enter}
|
73
|
+
EOT
|
74
|
+
$stdin.readline
|
75
|
+
end
|
76
|
+
|
77
|
+
def load_all!
|
78
|
+
dir = __FILE__.sub /\.rb$/, ''
|
79
|
+
Dir[dir + '/*.rb'].each do |e|
|
80
|
+
require e.sub /.*(tweemux\/.+)\.rb$/, '\1'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
Tweemux::Action.load_all!
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class Tweemux::Action::Forward < Tweemux::Action
|
2
|
+
def run args
|
3
|
+
usage_fail 'wrong number of thingies' unless 5 == args.size
|
4
|
+
host, hostport, from_, remote_host, port = args
|
5
|
+
usage_fail 'third thingy has to be numeric port' unless is_int? hostport
|
6
|
+
usage_fail 'fourth thingy has to be "from"' unless 'from' == from_
|
7
|
+
usage_fail 'fith thingy has to be numeric port' unless is_int? port
|
8
|
+
host = 'localhost' if 'local' == host
|
9
|
+
explained_run %W(ssh -fNR #{port}:#{host}:#{hostport} #{remote_host}),
|
10
|
+
'-f = background, -N = no remote command, -R = remote forward'
|
11
|
+
end
|
12
|
+
|
13
|
+
def usage_fail what_happened
|
14
|
+
raise FunkyUsage,
|
15
|
+
(what_happened+". Expected usage:\n ").color(:error_explanation) +
|
16
|
+
'tweemux '.color(:keyword) + colored_sample_command
|
17
|
+
end
|
18
|
+
|
19
|
+
def colored_sample_command
|
20
|
+
[
|
21
|
+
'forward'.color(:keyword),
|
22
|
+
'local'.color(:host),
|
23
|
+
'22'.color(:number),
|
24
|
+
'from'.color(:keyword),
|
25
|
+
'sharpsaw.org'.color(:host),
|
26
|
+
'3322'.color(:number),
|
27
|
+
].join ' '
|
28
|
+
end
|
29
|
+
|
30
|
+
def is_int? str
|
31
|
+
str.to_i.to_s == str
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class String
|
2
|
+
# Pallete viewable from:
|
3
|
+
# https://github.com/sharpsaw/tmux-dots/blob/master/bin/colortest
|
4
|
+
COLORS = {
|
5
|
+
:middle_blue => 69,
|
6
|
+
:brighty_blue => 39,
|
7
|
+
:gray245 => 245,
|
8
|
+
:orange => 172,
|
9
|
+
:pale_yellow => 180,
|
10
|
+
:lemon => 228,
|
11
|
+
|
12
|
+
:keypress => 157,
|
13
|
+
:prompt => 35,
|
14
|
+
:number => 48,
|
15
|
+
:keyword => 60,
|
16
|
+
:host => 148,
|
17
|
+
|
18
|
+
:error => 160,
|
19
|
+
:error_explanation => 178,
|
20
|
+
:default => 7
|
21
|
+
}
|
22
|
+
def color this_color, end_on = :default
|
23
|
+
[this_color, end_on].each{|e| fail "No color named #{e}" unless COLORS[e]}
|
24
|
+
if $stderr.tty?
|
25
|
+
"\e[38;5;#{COLORS[this_color]}m#{self}\e[38;5;#{COLORS[end_on]}m"
|
26
|
+
else
|
27
|
+
self
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
module TweemuxActionHelper
|
4
|
+
def test_run
|
5
|
+
explained_runs = []
|
6
|
+
Tweemux::Action.stub :explained_run, -> *a { explained_runs << a } do
|
7
|
+
Tweemux.run argv
|
8
|
+
end
|
9
|
+
got = explained_runs.map &:first
|
10
|
+
assert_equal got.to_yaml, expected_commands.to_yaml
|
11
|
+
end
|
12
|
+
|
13
|
+
def bad_runs; [] end
|
14
|
+
def test_bad_runs
|
15
|
+
bad_runs.each do |illegit|
|
16
|
+
begin
|
17
|
+
Tweemux.run illegit
|
18
|
+
rescue Tweemux::Action::FunkyUsage
|
19
|
+
else
|
20
|
+
fail "#{illegit} didn't raise expected error"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative '../../action_test_helper'
|
2
|
+
class Tweemux::Action::AtTest < MiniTest::Unit::TestCase
|
3
|
+
include TweemuxActionHelper
|
4
|
+
def argv; %w'at sharpsaw.org 2233' end
|
5
|
+
def expected_commands
|
6
|
+
[ %w(ssh sharpsaw.org -p2233 -t tmux -S /tmp/tweemux.sock attach) ]
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class Tweemux::Action::AtImplicitPortTest < MiniTest::Unit::TestCase
|
11
|
+
include TweemuxActionHelper
|
12
|
+
def argv; %w'at sharpsaw.org' end
|
13
|
+
def expected_commands
|
14
|
+
[ %w(ssh sharpsaw.org -p22 -t tmux -S /tmp/tweemux.sock attach) ]
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../../action_test_helper'
|
2
|
+
class Tweemux::Action::ForwardTest < MiniTest::Unit::TestCase
|
3
|
+
include TweemuxActionHelper
|
4
|
+
def argv; %w'forward local 22 from sharpsaw.org 3322' end
|
5
|
+
def expected_commands
|
6
|
+
[ %w(ssh -fNR 3322:localhost:22 sharpsaw.org) ]
|
7
|
+
end
|
8
|
+
|
9
|
+
def bad_runs
|
10
|
+
[
|
11
|
+
%w(forward asdf asdf),
|
12
|
+
%w(forward local hi from sharpsaw.org 3322),
|
13
|
+
%w(forward local 22 from sharpsaw.org hi),
|
14
|
+
%w(forward local 22 zipzip sharpsaw.org 3322),
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative '../../action_test_helper'
|
2
|
+
class Tweemux::Action::HostTest < MiniTest::Unit::TestCase
|
3
|
+
include TweemuxActionHelper
|
4
|
+
def argv; %w'host' end
|
5
|
+
def expected_commands
|
6
|
+
[
|
7
|
+
%w(tmux -S /tmp/tweemux.sock start-server),
|
8
|
+
%w(tmux -S /tmp/tweemux.sock new) + ['tweemux share'],
|
9
|
+
]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require_relative '../../action_test_helper'
|
2
|
+
class Tweemux::Action::ShareTest < MiniTest::Unit::TestCase
|
3
|
+
include TweemuxActionHelper
|
4
|
+
def argv; %w'share' end
|
5
|
+
def expected_commands
|
6
|
+
[
|
7
|
+
%w(chmod a+rw /tmp/tweemux.sock),
|
8
|
+
[ENV['SHELL']],
|
9
|
+
]
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
class Tweemux::ActionTest < MiniTest::Unit::TestCase
|
4
|
+
def test_explained_run_strictness
|
5
|
+
Tweemux::Action.explained_run 'echo > /tmp/hi', 'not shell-interpreted'
|
6
|
+
rescue Tweemux::Action::DubiousSystemInvocation
|
7
|
+
else
|
8
|
+
fail 'Should have refused to run a sketchy shell command.'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class TweemuxTest < MiniTest::Unit::TestCase
|
4
|
+
def test_doc_claims
|
5
|
+
claimed_working = File.readlines('README.rdoc').grep /^ {4}tweemux/
|
6
|
+
claimed_working.each do |line|
|
7
|
+
line.sub! /#.*/, ''
|
8
|
+
fake_argv = line.sub(/^\s*tweemux/, '').split.map{|e| e.strip}
|
9
|
+
action = Tweemux.understand fake_argv
|
10
|
+
got_run = false
|
11
|
+
action.stub :run, -> *a { got_run = true } do
|
12
|
+
action.call
|
13
|
+
end
|
14
|
+
assert got_run, "running action for #{line}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_ruby18
|
19
|
+
# TODO: optimize this by running them all in one command
|
20
|
+
Dir['{bin,lib}/*.rb'].each do |e|
|
21
|
+
fail "ruby18 hates #{e}" unless "Syntax OK\n" == `ruby18 -c #{e}`
|
22
|
+
end
|
23
|
+
rescue Errno::ENOENT => e
|
24
|
+
skip 'Needs ruby18 executable in $PATH' if e.message[/ruby18/]
|
25
|
+
raise e
|
26
|
+
end
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tweemux
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- ☈king
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-02-25 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'For Remote Pair Programming: A handy script to create-or-join a world-readable
|
15
|
+
tmux session (smaller than wemux)
|
16
|
+
|
17
|
+
|
18
|
+
For Remote Pair Programming: A handy script to create-or-join a world-readable tmux
|
19
|
+
session (smaller than wemux)
|
20
|
+
|
21
|
+
'
|
22
|
+
email: rking-tweemux@sharpsaw.org
|
23
|
+
executables:
|
24
|
+
- tweemux
|
25
|
+
extensions: []
|
26
|
+
extra_rdoc_files: []
|
27
|
+
files:
|
28
|
+
- .gemspec
|
29
|
+
- .gitignore
|
30
|
+
- Gemfile
|
31
|
+
- Guardfile
|
32
|
+
- LICENSE.txt
|
33
|
+
- README.rdoc
|
34
|
+
- Rakefile
|
35
|
+
- bin/tweemux
|
36
|
+
- lib/tweemux.rb
|
37
|
+
- lib/tweemux/action.rb
|
38
|
+
- lib/tweemux/action/at.rb
|
39
|
+
- lib/tweemux/action/bro.rb
|
40
|
+
- lib/tweemux/action/forward.rb
|
41
|
+
- lib/tweemux/action/host.rb
|
42
|
+
- lib/tweemux/action/share.rb
|
43
|
+
- lib/tweemux/action/sis.rb
|
44
|
+
- lib/tweemux/core_ext.rb
|
45
|
+
- lib/tweemux/version.rb
|
46
|
+
- test/action_test_helper.rb
|
47
|
+
- test/test_helper.rb
|
48
|
+
- test/tweemux/action/at_test.rb
|
49
|
+
- test/tweemux/action/forward_test.rb
|
50
|
+
- test/tweemux/action/host_test.rb
|
51
|
+
- test/tweemux/action/share_test.rb
|
52
|
+
- test/tweemux/action_test.rb
|
53
|
+
- test/tweemux_test.rb
|
54
|
+
homepage: https://github.com/rking/tweemux
|
55
|
+
licenses: []
|
56
|
+
post_install_message:
|
57
|
+
rdoc_options: []
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ! '>='
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 1.8.24
|
75
|
+
signing_key:
|
76
|
+
specification_version: 3
|
77
|
+
summary: ! 'For Remote Pair Programming: A handy script to create-or-join a world-readable
|
78
|
+
tmux session (smaller than wemux)'
|
79
|
+
test_files:
|
80
|
+
- test/action_test_helper.rb
|
81
|
+
- test/test_helper.rb
|
82
|
+
- test/tweemux/action/at_test.rb
|
83
|
+
- test/tweemux/action/forward_test.rb
|
84
|
+
- test/tweemux/action/host_test.rb
|
85
|
+
- test/tweemux/action/share_test.rb
|
86
|
+
- test/tweemux/action_test.rb
|
87
|
+
- test/tweemux_test.rb
|
88
|
+
has_rdoc:
|