tweemux 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ )
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+ group :development do
3
+ gem 'working'
4
+ end
@@ -0,0 +1 @@
1
+ require 'working/guard'
@@ -0,0 +1 @@
1
+ Copyleft (ↄ) 2013 ☈king — CC0/Public Domain.
@@ -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.
@@ -0,0 +1 @@
1
+ require 'working/rake_tasks'
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'tweemux'
3
+ Tweemux.run ARGV
@@ -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,8 @@
1
+ class Tweemux::Action::At < Tweemux::Action
2
+ def run args
3
+ host, port = args
4
+ port ||= 22
5
+ explained_run %W(ssh #{host} -p#{port} -t tmux -S #{Tweemux::SOCK} attach),
6
+ "Connect to #{host} on port #{port}, demand a pty, then attach to session"
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ class Tweemux::Action::Bro < Tweemux::Action
2
+
3
+ end
@@ -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,7 @@
1
+ class Tweemux::Action::Host < Tweemux::Action
2
+ def run _
3
+ tmux_S %w'start-server', 'brings up the tmux process'
4
+ tmux_S ['new', 'tweemux share'],
5
+ "starts sesssion '0', repermissionizes, then runs '#{ENV['SHELL']}'"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ class Tweemux::Action::Share < Tweemux::Action
2
+ def run _
3
+ chmod_a_rw = %w(chmod a+rw) + [Tweemux::SOCK]
4
+ explained_run chmod_a_rw, 'makes the shared socket shareable'
5
+ explained_run [ENV['SHELL']], 'only here to prevent the session from ending'
6
+ end
7
+ end
@@ -0,0 +1,2 @@
1
+ require 'tweemux/action/bro'
2
+ class Tweemux::Action::Sis < Tweemux::Action::Bro; 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,3 @@
1
+ class Tweemux
2
+ VERSION = '0.0.1'
3
+ 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
@@ -0,0 +1,9 @@
1
+ # Run this via 'beg'
2
+ require 'working/test_helper'
3
+
4
+ require 'tweemux'
5
+
6
+ Spork.each_run do
7
+ Dir['lib/**/*.rb'].each{|e| load e}
8
+ end
9
+ # Spork.prefork doesn't like when this is missing
@@ -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: