wormholes 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.
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