wormholes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Aaron Royer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # wormholes
2
+
3
+ shell utility that warps text elsewhere
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ $ gem install wormholes
9
+ ```
10
+ Make sure to include the "s". The singular form is something else entirely.
11
+
12
+ ## Usage
13
+
14
+ Open one end of the wormhole with ```worm```. You can pipe any text into it you would like but a wormhole is generally best suited for some progressive, long-running output like a server log.
15
+
16
+ ```
17
+ $ rails server | worm
18
+ ```
19
+ At this point whatever is piped into the wormhole does not go anywhere. It disappears. You could say it goes into a black hole, I suppose.
20
+
21
+ If you want to start seeing any of that text again, open the other end with ```hole```.
22
+
23
+ ```
24
+ $ hole
25
+ Started GET "/" for 127.0.0.1 at 2010-09-10 21:07:07 +1000
26
+ Processing by UsersController#index as HTML
27
+ SQL (0.5ms) SELECT name
28
+ FROM sqlite_master
29
+ WHERE type = 'table' AND NOT name = 'sqlite_sequence'
30
+ User Load (0.5ms) SELECT "users".* FROM "users" LIMIT 15 OFFSET 0
31
+ SQL (1.4ms) SELECT COUNT(*) AS count_id FROM "users"
32
+ Rendered users/index.html.erb within layouts/application (24.8ms)
33
+ Completed 200 OK in 51ms (Views: 32.9ms | ActiveRecord: 2.3ms)
34
+ ...
35
+ ```
36
+
37
+ You can kill ```hole``` with Ctrl-C when you want to stop seeing the output. As long as ```worm``` is running the wormhole will be open and you can fire up ```hole``` again to see what is going into the ```worm``` end.
38
+
39
+ You can open as many ```hole```s as you would like, at the same time, and use them however you would use the original program producing the output.
40
+
41
+ ```
42
+ $ hole | grep Rendered
43
+ Rendered users/index.html.erb within layouts/application (24.8ms)
44
+
45
+ ```
46
+ Meanwhile in another shell…
47
+
48
+ ```
49
+ $ hole | grep SQL
50
+ SQL (0.5ms) SELECT name
51
+ SQL (1.4ms) SELECT COUNT(*) AS count_id FROM "users"
52
+ ```
53
+
54
+ ### Named wormholes
55
+
56
+ So far you can only create one wormhole at a time, with one program piping its output into ```worm```. If you open another wormhole while the previous one is still open it will stop the other one. But you might want to open more than one at a time. To do this you can give a wormhole a name and refer to it when you open the other end.
57
+
58
+ ```
59
+ $ yes | worm test
60
+ ```
61
+ This creates a wormhole with the name 'test'. Open the other end with the same name.
62
+
63
+ ```
64
+ $ hole test
65
+ y
66
+ y
67
+ y
68
+ ...
69
+ ```
70
+
71
+ This allows you to create as many wormholes as you want, at the same time, as long as they have unique names.
72
+
73
+ ## Why use this?
74
+
75
+ Maybe you can think of ways to use this that I have not thought of. Getting log output wherever I want it, on demand, without restarting a server was the original use case.
76
+
77
+ So I use it for things like having multiple ```grep```s running on log output (as above). Or to tuck away a running server in another shell and just ```hole | grep something``` in the shell I'm working in to grab the few lines I'm looking for and Ctrl-C it away and get back to what I was doing.
78
+
79
+ All of these things can probably be done in other ways with the nice UNIX tools we know and love. If you have a file being appended to you can ```tail -f``` a bunch of times and get multiple outputs of your text. You can do stuff with ```tee```. You can use named pipes and sockets (wormholes uses UNIX sockets under the hood). You can think of wormholes as a formalization of some other ad hoc techniques.
80
+
81
+ ## License
82
+
83
+ wormholes is MIT licensed
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test]
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'lib'
8
+ t.pattern = 'test/**/*_test.rb'
9
+ end
10
+
data/bin/hole ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # Usage: hole [NAME]
3
+ #
4
+ # Opens the other end of an existing wormhole. Pipe text into worm and use hole
5
+ # to see the text coming out in real-time.
6
+ #
7
+ # NAME is an optional name to give the created wormhole. If you use a name
8
+ # with worm then use the same name with hole to open the other end of the
9
+ # named wormhole.
10
+ #
11
+ # To use, pipe output to worm
12
+ #
13
+ # $ output_producing_program | worm
14
+ #
15
+ # Then, in another shell, get the output with hole
16
+ #
17
+ # $ hole
18
+ # <output comes out here...>
19
+ #
20
+ require 'optparse'
21
+
22
+ OptionParser.new do |opts|
23
+ opts.on('-h', '--help') do
24
+ IO.read(__FILE__).each_line do |line|
25
+ break unless line =~ /^#/
26
+ puts line[2..-1] unless line =~ /^#!/
27
+ end
28
+ exit
29
+ end
30
+ end.parse!
31
+
32
+
33
+ require 'wormholes'
34
+
35
+ opts = {}
36
+ opts[:name] = ARGV[0] if ARGV[0]
37
+
38
+ hole = Wormholes::Hole.new opts
39
+ hole.open
40
+
41
+ trap('EXIT') { hole.close }
42
+
43
+ hole.listen
data/bin/worm ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env ruby
2
+ # Usage: worm [NAME]
3
+ #
4
+ # Creates a wormhole for text. Pipe text into worm and use the corresponding
5
+ # hole utility to see the text coming out in real-time.
6
+ #
7
+ # NAME is an optional name to give the created wormhole. If you use a name
8
+ # with worm then use the same name with hole to open the other end of the
9
+ # named wormhole.
10
+ #
11
+ # To use, pipe output to worm
12
+ #
13
+ # $ output_producing_program | worm
14
+ #
15
+ # Then, in another shell, get the output with hole
16
+ #
17
+ # $ hole
18
+ # <output comes out here...>
19
+ #
20
+ require 'optparse'
21
+
22
+ OptionParser.new do |opts|
23
+ opts.on('-h', '--help') do
24
+ IO.read(__FILE__).each_line do |line|
25
+ break unless line =~ /^#/
26
+ puts line[2..-1] unless line =~ /^#!/
27
+ end
28
+ exit
29
+ end
30
+ end.parse!
31
+
32
+
33
+ require 'wormholes'
34
+
35
+ opts = {}
36
+ opts[:name] = ARGV[0] if ARGV[0]
37
+
38
+ worm = Wormholes::Worm.new opts
39
+ worm.open
40
+
41
+ trap('EXIT') { worm.close }
42
+
43
+ worm.send_stream($stdin)
data/lib/wormholes.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'wormholes/version'
2
+ require 'wormholes/utilities'
3
+ require 'wormholes/worm'
4
+ require 'wormholes/hole'
5
+
6
+ module Wormholes
7
+ NAME_REGEX = /^[\w\d_-]+$/i
8
+
9
+ def self.socket_dir
10
+ ENV['WORMHOLES_SOCKET_DIR'] || File.join(ENV['HOME'], '.wormholes-sockets')
11
+ end
12
+ end
@@ -0,0 +1,40 @@
1
+ require 'socket'
2
+
3
+ module Wormholes
4
+ class Hole
5
+ include Utilities
6
+
7
+ attr_accessor :name
8
+
9
+ def initialize(opts={})
10
+ @name = opts[:name] ? test_wormhole_name(opts[:name]) : 'default'
11
+ @socket_file = opts[:socket_file] || File.join(::Wormholes.socket_dir, "#{@name}.sock")
12
+ @output_stream = opts[:output_stream] || $stdout
13
+ @socket = nil
14
+ end
15
+
16
+ def open
17
+ @socket = UNIXSocket.new(@socket_file)
18
+ end
19
+
20
+ def close
21
+ @socket.close
22
+ end
23
+
24
+ def listen
25
+ begin
26
+ while listen_for_next_line
27
+ # no-op
28
+ end
29
+ rescue Interrupt
30
+ close
31
+ end
32
+ end
33
+
34
+ def listen_for_next_line
35
+ line = @socket.gets
36
+ @output_stream.puts line
37
+ line
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module Wormholes
2
+
3
+ # Stuff that is needed by both ends of the wormhole
4
+ module Utilities
5
+ def test_wormhole_name(name)
6
+ unless proper_wormhole_name?(name)
7
+ raise ArgumentError, 'Wormhole name must only contain letters, numbers, hyphens, or underscores'
8
+ end
9
+ name
10
+ end
11
+
12
+ def proper_wormhole_name?(name)
13
+ NAME_REGEX =~ name
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Wormholes
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,103 @@
1
+ require 'socket'
2
+ require 'fileutils'
3
+
4
+ module Wormholes
5
+ class Worm
6
+ include Utilities
7
+
8
+ attr_accessor :server, :sockets, :name
9
+
10
+ def initialize(opts={})
11
+ @name = opts[:name] ? test_wormhole_name(opts[:name]) : 'default'
12
+ @socket_file = opts[:socket_file] || File.join(::Wormholes.socket_dir, "#{@name}.sock")
13
+ @sockets = []
14
+ @server = nil
15
+
16
+ @verbose = opts[:verbose] || false
17
+ @debug = opts[:debug] || false
18
+ end
19
+
20
+ def open
21
+ socket_dir = File.dirname(@socket_file)
22
+ FileUtils.mkdir_p(socket_dir) unless File.directory?(socket_dir)
23
+
24
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
25
+ @server = UNIXServer.new(@socket_file)
26
+ accept_any_connection
27
+ update_status
28
+ end
29
+
30
+ def close
31
+ @server.close
32
+ @server = nil
33
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
34
+ $stderr.puts "\n...wormhole closed" if @verbose
35
+ end
36
+
37
+ def send_to_clients(msg)
38
+ disconnected_sockets = []
39
+ @sockets.each do |s|
40
+ begin
41
+ s.puts msg
42
+ rescue SystemCallError => e
43
+ $stderr.puts "Client disconnected: #{s}" if @debug
44
+ disconnected_sockets << s
45
+ end
46
+ end
47
+ unless disconnected_sockets.empty?
48
+ @sockets = @sockets - disconnected_sockets
49
+ debug_sockets
50
+ update_status
51
+ end
52
+ end
53
+
54
+ def send_stream(stream=$stdin)
55
+ begin
56
+ while line = stream.gets
57
+ send_to_clients(line)
58
+ end
59
+ rescue Interrupt
60
+ close
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def accept_any_connection
67
+ $worm = self
68
+ Thread.new do
69
+ while $worm.server
70
+ $worm.send(:accept_connection)
71
+ end
72
+ end
73
+ end
74
+
75
+ def accept_connection
76
+ socket = @server.accept
77
+ add_client socket
78
+ update_status
79
+ end
80
+
81
+ def add_client(socket)
82
+ $stderr.puts "Client connected: #{socket.inspect}" if @debug
83
+ @sockets << socket
84
+ debug_sockets
85
+ end
86
+
87
+ def update_status
88
+ return unless @verbose
89
+ @previous_status ||= ''
90
+ @previous_status.length.times { print "\b" }
91
+ status = "Clients: #{sockets.size}"
92
+ print status
93
+ @previous_status = status
94
+ end
95
+
96
+ def debug_sockets
97
+ return unless @debug
98
+ $stderr.puts "=== Sockets ==="
99
+ @sockets.each {|s| $stderr.puts s.inspect}
100
+ $stderr.puts "==============="
101
+ end
102
+ end
103
+ end
data/test/hole_test.rb ADDED
@@ -0,0 +1,10 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class HoleTest < MiniTest::Unit::TestCase
4
+ def test_proper_wormhole_names_are_enforced
5
+ GOOD_WORMHOLE_NAMES.each {|name| Wormholes::Hole.new :name => name }
6
+ BAD_WORMHOLE_NAMES.each do |name|
7
+ assert_raises(ArgumentError) { Wormholes::Hole.new :name => name }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+ require 'fileutils'
3
+ require 'tmpdir'
4
+ require 'stringio'
5
+
6
+ # Test wormholes using normal UNIX domain sockets
7
+ class SocketWormholeTest < MiniTest::Unit::TestCase
8
+ def setup
9
+ @tmp_dir = File.join( Dir.tmpdir, "wormholes-tests-#{(0...10).map{ ('a'..'z').to_a[rand(26)] }.join}")
10
+ Dir.mkdir(@tmp_dir)
11
+ end
12
+
13
+ def teardown
14
+ FileUtils.rm_rf @tmp_dir
15
+ end
16
+
17
+ def test_a_line_can_be_sent
18
+ test_str = "this is what we will send\n"
19
+ str_io = StringIO.new
20
+
21
+ ENV['WORMHOLES_SOCKET_DIR'] = @tmp_dir
22
+ worm = Wormholes::Worm.new
23
+ worm.open
24
+
25
+ socket_file = File.join(@tmp_dir, 'default.sock')
26
+ assert File.exist?(socket_file), 'the socket file has been created'
27
+ assert File.socket?(socket_file), 'the socket file is, indeed, a socket'
28
+
29
+ hole = Wormholes::Hole.new(:output_stream => str_io)
30
+ hole.open
31
+
32
+ sleep 0.01 while worm.sockets.size < 1
33
+
34
+ thread = Thread.new { hole.listen_for_next_line }
35
+ worm.send_to_clients test_str
36
+ thread.join
37
+ worm.close
38
+
39
+ assert_equal test_str, str_io.string
40
+ end
41
+
42
+ def test_can_use_named_wormholes
43
+ test_str = "we will send this through the named wormhole\n"
44
+ str_io = StringIO.new
45
+ wormhole_name = 'deep-space-9'
46
+
47
+ ENV['WORMHOLES_SOCKET_DIR'] = @tmp_dir
48
+ worm = Wormholes::Worm.new(:name => wormhole_name)
49
+ worm.open
50
+
51
+ socket_file = File.join(@tmp_dir, "#{wormhole_name}.sock")
52
+ assert File.exist?(socket_file), 'the socket file has been created'
53
+ assert File.socket?(socket_file), 'the socket file is, indeed, a socket'
54
+
55
+ hole = Wormholes::Hole.new(:name => wormhole_name, :output_stream => str_io)
56
+ hole.open
57
+
58
+ sleep 0.01 while worm.sockets.size < 1
59
+
60
+ thread = Thread.new { hole.listen_for_next_line }
61
+ worm.send_to_clients test_str
62
+ thread.join
63
+ worm.close
64
+
65
+ assert_equal test_str, str_io.string
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'minitest/autorun'
3
+
4
+ require File.join(File.dirname(__FILE__), *%w[.. lib wormholes])
5
+
6
+ GOOD_WORMHOLE_NAMES = %w[name a johnny5 500 2 default]
7
+ BAD_WORMHOLE_NAMES = ['', 'two words', 'weird(name)', '#name', 'myemail@aol.com']
data/test/worm_test.rb ADDED
@@ -0,0 +1,10 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class WormTest < MiniTest::Unit::TestCase
4
+ def test_proper_wormhole_names_are_enforced
5
+ GOOD_WORMHOLE_NAMES.each {|name| Wormholes::Worm.new :name => name }
6
+ BAD_WORMHOLE_NAMES.each do |name|
7
+ assert_raises(ArgumentError) { Wormholes::Worm.new :name => name }
8
+ end
9
+ end
10
+ end
data/wormholes.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wormholes/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "wormholes"
8
+ gem.version = Wormholes::VERSION
9
+ gem.authors = ["Aaron Royer"]
10
+ gem.email = ["aaronroyer@gmail.com"]
11
+ gem.description = %q{shell utility that warps text elsewhere}
12
+ gem.summary = "Pipe text (like log output) in one end and output it anywhere else."
13
+ gem.homepage = "https://github.com/aaronroyer/wormholes"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wormholes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Royer
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-27 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: shell utility that warps text elsewhere
15
+ email:
16
+ - aaronroyer@gmail.com
17
+ executables:
18
+ - hole
19
+ - worm
20
+ extensions: []
21
+ extra_rdoc_files: []
22
+ files:
23
+ - .gitignore
24
+ - Gemfile
25
+ - LICENSE.txt
26
+ - README.md
27
+ - Rakefile
28
+ - bin/hole
29
+ - bin/worm
30
+ - lib/wormholes.rb
31
+ - lib/wormholes/hole.rb
32
+ - lib/wormholes/utilities.rb
33
+ - lib/wormholes/version.rb
34
+ - lib/wormholes/worm.rb
35
+ - test/hole_test.rb
36
+ - test/socket_wormhole_test.rb
37
+ - test/test_helper.rb
38
+ - test/worm_test.rb
39
+ - wormholes.gemspec
40
+ homepage: https://github.com/aaronroyer/wormholes
41
+ licenses: []
42
+ post_install_message:
43
+ rdoc_options: []
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ none: false
48
+ requirements:
49
+ - - ! '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 1.8.23
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Pipe text (like log output) in one end and output it anywhere else.
64
+ test_files:
65
+ - test/hole_test.rb
66
+ - test/socket_wormhole_test.rb
67
+ - test/test_helper.rb
68
+ - test/worm_test.rb