tweemux 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.
@@ -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: