terminitor 0.4.1 → 0.5.0
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/Gemfile.lock +2 -4
- data/LICENSE +20 -0
- data/README.md +31 -4
- data/lib/terminitor.rb +12 -1
- data/lib/terminitor/abstract_core.rb +17 -6
- data/lib/terminitor/capture/cmd_capture.rb +7 -0
- data/lib/terminitor/capture/terminator_capture.rb +9 -0
- data/lib/terminitor/cli.rb +13 -2
- data/lib/terminitor/cores/cmd_core/cmd_core.rb +35 -0
- data/lib/terminitor/cores/cmd_core/input.rb +273 -0
- data/lib/terminitor/cores/cmd_core/windows_console.rb +83 -0
- data/lib/terminitor/cores/terminator_core.rb +117 -0
- data/lib/terminitor/dsl.rb +29 -12
- data/lib/terminitor/runner.rb +48 -14
- data/lib/terminitor/version.rb +1 -1
- data/lib/terminitor/yaml.rb +2 -0
- data/terminitor.gemspec +21 -10
- data/test.watchr +9 -5
- data/test/capture/mac_capture_test.rb +2 -6
- data/test/cli_test.rb +54 -22
- data/test/cores/cmd_core_test.rb +51 -0
- data/test/cores/iterm_core_test.rb +2 -5
- data/test/cores/konsole_core_test.rb +1 -1
- data/test/cores/mac_core_test.rb +12 -11
- data/test/cores/terminator_core_test.rb +39 -0
- data/test/dsl_test.rb +79 -25
- data/test/runner_test.rb +47 -24
- data/test/teststrap.rb +15 -7
- metadata +31 -87
- data/templates/example.yml.tt +0 -16
@@ -0,0 +1,83 @@
|
|
1
|
+
module Terminitor
|
2
|
+
|
3
|
+
class CurrentWindowsConsole
|
4
|
+
include Input
|
5
|
+
def send_command(cmd)
|
6
|
+
# puts "[current] #{cmd}"
|
7
|
+
type_in cmd
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
class WindowsConsole
|
14
|
+
include Windows::Process
|
15
|
+
include Windows::Handle
|
16
|
+
include Windows::Window
|
17
|
+
include Input
|
18
|
+
|
19
|
+
Windows::API.new('SetForegroundWindow', 'L', 'I', 'user32')
|
20
|
+
|
21
|
+
attr_accessor :name
|
22
|
+
attr_accessor :parameters
|
23
|
+
attr_accessor :title
|
24
|
+
attr_reader :pid
|
25
|
+
attr_reader :thread_id
|
26
|
+
attr_reader :hwnd
|
27
|
+
|
28
|
+
def initialize(options = {})
|
29
|
+
@name = options[:name] || name
|
30
|
+
@title = options[:title] || name
|
31
|
+
@parameters = options[:parameters]
|
32
|
+
|
33
|
+
start
|
34
|
+
end
|
35
|
+
|
36
|
+
def start
|
37
|
+
command_line = name
|
38
|
+
command_line = name + ' ' + parameters if parameters
|
39
|
+
|
40
|
+
# returns a struct, raises an error if fails
|
41
|
+
process_info = Process.create(
|
42
|
+
:command_line => command_line,
|
43
|
+
:close_handles => false,
|
44
|
+
:creation_flags => Process::CREATE_NEW_CONSOLE
|
45
|
+
)
|
46
|
+
@pid = process_info.process_id
|
47
|
+
@thread_id = process_info.thread_id
|
48
|
+
@process_handle = process_info.process_handle
|
49
|
+
@thread_handle = process_info.thread_handle
|
50
|
+
|
51
|
+
@pid
|
52
|
+
end
|
53
|
+
|
54
|
+
def kill!
|
55
|
+
CloseHandle(@process_handle)
|
56
|
+
CloseHandle(@thread_handle)
|
57
|
+
|
58
|
+
Process::kill(9, pid)
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_window(process_id)
|
62
|
+
sleep 0.4 #todo - find a better way to wait for console to show up!!
|
63
|
+
child_after = 0
|
64
|
+
while (child_after = FindWindowEx(nil, child_after, nil, nil)) > 0 do
|
65
|
+
process_id = 0.chr * 4
|
66
|
+
GetWindowThreadProcessId(child_after, process_id)
|
67
|
+
process_id = process_id.unpack('L').first
|
68
|
+
return child_after if process_id == @pid
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def send_command(cmd)
|
76
|
+
@hwnd ||= find_window(@thread_id)
|
77
|
+
SetForegroundWindow(@hwnd)
|
78
|
+
# puts "[#{@hwnd}] #{cmd}"
|
79
|
+
type_in(cmd)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Terminitor
|
2
|
+
|
3
|
+
# Terminator core for Terminitor.
|
4
|
+
#
|
5
|
+
# Since Terminator doesn't have a complete IPC interface,
|
6
|
+
# everything is done by using "xdotool" to simulate keypresses.
|
7
|
+
class TerminatorCore < AbstractCore
|
8
|
+
def initialize(path)
|
9
|
+
abort("xdotool required for Terminator support") if (@xdotool = `which xdotool`.chomp).empty?
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute_command(cmd, options = {})
|
14
|
+
# add a cr to the end if missing
|
15
|
+
cmd += "\n" if (cmd =~ /\n\Z/).nil?
|
16
|
+
run_in_active_terminator cmd, options
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def open_tab(options = nil)
|
21
|
+
send_keypress "ctrl+shift+t", options
|
22
|
+
end
|
23
|
+
|
24
|
+
def open_window(options = nil)
|
25
|
+
send_keypress "ctrl+shift+i", options
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
# Run arbitrary command in active terminator window.
|
31
|
+
# abort()s if the active window is not a terminator window.
|
32
|
+
#
|
33
|
+
# @param cmd [String] command.
|
34
|
+
# @param options [Hash] options hash.
|
35
|
+
def run_in_active_terminator(cmd, options)
|
36
|
+
winid = window_id
|
37
|
+
type_in_window winid, cmd
|
38
|
+
end
|
39
|
+
|
40
|
+
# Send keypresses to active window.
|
41
|
+
#
|
42
|
+
# @param keys [String] keypresses in the form expected by xdotool.
|
43
|
+
# @param options [Hash] options hash from terminitor.
|
44
|
+
def send_keypress(keys, options)
|
45
|
+
winid = window_id
|
46
|
+
xdotool("key --window #{winid} #{keys}")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get the X11 window ID of the active terminator window.
|
50
|
+
#
|
51
|
+
# If the active window is not a terminator window, abort.
|
52
|
+
#
|
53
|
+
# @return [String] X11 window ID of active terminator window.
|
54
|
+
def window_id
|
55
|
+
active = get_active_window
|
56
|
+
if get_terminator_windows.include? active
|
57
|
+
return active
|
58
|
+
else
|
59
|
+
abort("not running in Terminator")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Use xdotool to get the window id of the active window.
|
64
|
+
#
|
65
|
+
# @return [String] active X11 window ID.
|
66
|
+
def get_active_window
|
67
|
+
xdotool("getactivewindow").chomp
|
68
|
+
end
|
69
|
+
|
70
|
+
# Use xdotool to get the window IDs of all visible
|
71
|
+
# terminator windows.
|
72
|
+
#
|
73
|
+
# @return [Array] String array of terminator window IDs.
|
74
|
+
def get_terminator_windows
|
75
|
+
xdotool("search --onlyvisible --class terminator").split("\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
# Focus window with given id.
|
79
|
+
#
|
80
|
+
# @param id [String] X11 window id.
|
81
|
+
def focus_window(id)
|
82
|
+
xdotool("windowfocus #{id}")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Raise window with given id.
|
86
|
+
#
|
87
|
+
# @param id [String] X11 window id.
|
88
|
+
def raise_window(id)
|
89
|
+
xdotool("windowraise #{id}")
|
90
|
+
end
|
91
|
+
|
92
|
+
# Send arbitrary text to given window.
|
93
|
+
#
|
94
|
+
# @param winid [String] X11 window id.
|
95
|
+
# @param text [String] text to type.
|
96
|
+
def type_in_window(winid, text)
|
97
|
+
xdotool("type --window #{winid} \"#{text}\"")
|
98
|
+
end
|
99
|
+
|
100
|
+
# Run arbitrary xdotool commands.
|
101
|
+
#
|
102
|
+
# @param cmd [String] command as string.
|
103
|
+
# @return [String] result as string.
|
104
|
+
def xdotool(cmd)
|
105
|
+
execute("#{@xdotool} #{cmd}")
|
106
|
+
end
|
107
|
+
|
108
|
+
# Execute arbitrary command and return result string.
|
109
|
+
#
|
110
|
+
# @param cmd [String] command string.
|
111
|
+
# @return [String] result as string.
|
112
|
+
def execute(cmd)
|
113
|
+
IO.popen(cmd).read
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
data/lib/terminitor/dsl.rb
CHANGED
@@ -2,6 +2,7 @@ module Terminitor
|
|
2
2
|
# This class parses the Termfile to fit the new Ruby Dsl Syntax
|
3
3
|
class Dsl
|
4
4
|
|
5
|
+
# @param [String] path to termfile
|
5
6
|
def initialize(path)
|
6
7
|
file = File.read(path)
|
7
8
|
@setup = []
|
@@ -12,8 +13,11 @@ module Terminitor
|
|
12
13
|
|
13
14
|
# Contains all commands that will be run prior to the usual 'workflow'
|
14
15
|
# e.g bundle install, setup forks, etc ...
|
15
|
-
#
|
16
|
-
#
|
16
|
+
# @param [Array<String>] array of commands
|
17
|
+
# @param [Proc]
|
18
|
+
# @example
|
19
|
+
# setup "bundle install", "brew update"
|
20
|
+
# setup { run('bundle install') }
|
17
21
|
def setup(*commands, &block)
|
18
22
|
if block_given?
|
19
23
|
in_context @setup, &block
|
@@ -23,8 +27,11 @@ module Terminitor
|
|
23
27
|
end
|
24
28
|
|
25
29
|
# sets command context to be run inside a specific window
|
26
|
-
#
|
27
|
-
#
|
30
|
+
# @param [Hash] options hash.
|
31
|
+
# @param [Proc]
|
32
|
+
# @example
|
33
|
+
# window(:name => 'new window', :size => [80,30], :position => [9, 100]) { tab('ls','gitx') }
|
34
|
+
# window { tab('ls', 'gitx') }
|
28
35
|
def window(options = {}, &block)
|
29
36
|
window_name = "window#{@windows.keys.size}"
|
30
37
|
window_contents = @windows[window_name] = {:tabs => {'default' => {:commands =>[]}}}
|
@@ -33,20 +40,26 @@ module Terminitor
|
|
33
40
|
end
|
34
41
|
|
35
42
|
# stores command in context
|
36
|
-
#
|
43
|
+
# @param [Array<String>] Array of commands
|
44
|
+
# @example
|
45
|
+
# run 'brew update'
|
37
46
|
def run(*commands)
|
38
|
-
|
47
|
+
# if we are in a window context, append commands to default tab.
|
48
|
+
if @_context.is_a?(Hash) && @_context[:tabs]
|
39
49
|
current = @_context[:tabs]['default'][:commands]
|
40
50
|
else
|
41
51
|
current = @_context
|
42
52
|
end
|
43
|
-
current << commands.join(" && ")
|
53
|
+
current << commands.map { |c| "(#{c})" }.join(" && ")
|
44
54
|
end
|
45
55
|
|
46
56
|
# runs commands before each tab in window context
|
47
|
-
#
|
48
|
-
#
|
49
|
-
#
|
57
|
+
# @param [Array<String>] Array of commands
|
58
|
+
# @param [Proc]
|
59
|
+
# @example
|
60
|
+
# window do
|
61
|
+
# before { run 'whoami' }
|
62
|
+
# end
|
50
63
|
def before(*commands, &block)
|
51
64
|
@_context[:before] ||= []
|
52
65
|
if block_given?
|
@@ -57,8 +70,11 @@ module Terminitor
|
|
57
70
|
end
|
58
71
|
|
59
72
|
# sets command context to be run inside specific tab
|
60
|
-
#
|
61
|
-
#
|
73
|
+
# @param [Array<String>] Array of commands
|
74
|
+
# @param [Proc]
|
75
|
+
# @example
|
76
|
+
# tab(:name => 'new tab', :settings => 'Grass') { run 'mate .' }
|
77
|
+
# tab 'ls', 'gitx'
|
62
78
|
def tab(*args, &block)
|
63
79
|
tabs = @_context[:tabs]
|
64
80
|
tab_name = "tab#{tabs.keys.size}"
|
@@ -78,6 +94,7 @@ module Terminitor
|
|
78
94
|
end
|
79
95
|
|
80
96
|
# Returns yaml file as Terminitor formmatted hash
|
97
|
+
# @return [Hash] Return hash format of Termfile
|
81
98
|
def to_hash
|
82
99
|
{ :setup => @setup, :windows => @windows }
|
83
100
|
end
|
data/lib/terminitor/runner.rb
CHANGED
@@ -2,8 +2,13 @@ module Terminitor
|
|
2
2
|
# This module contains all the helper methods for the Cli component.
|
3
3
|
module Runner
|
4
4
|
|
5
|
+
# Terminitor Global Path
|
6
|
+
TERM_PATH = File.join(ENV['HOME'],'.config','terminitor')
|
7
|
+
|
5
8
|
# Finds the appropriate platform core, else say you don't got it.
|
6
|
-
#
|
9
|
+
# @param [String] the ruby platform
|
10
|
+
# @example
|
11
|
+
# find_core RUBY_PLATFORM
|
7
12
|
def find_core(platform)
|
8
13
|
core = case platform.downcase
|
9
14
|
when %r{darwin} then
|
@@ -12,12 +17,20 @@ module Terminitor
|
|
12
17
|
else
|
13
18
|
Terminitor::MacCore
|
14
19
|
end
|
15
|
-
when %r{linux} then
|
20
|
+
when %r{linux} then
|
21
|
+
if not `which terminator`.chomp.empty?
|
22
|
+
Terminitor::TerminatorCore
|
23
|
+
else
|
24
|
+
Terminitor::KonsoleCore # TODO silly fallback, make better check
|
25
|
+
end
|
26
|
+
when %r{mswin|mingw} then
|
27
|
+
Terminitor::CmdCore
|
16
28
|
else nil
|
17
29
|
end
|
18
30
|
end
|
19
31
|
|
20
32
|
# Defines how to capture terminal settings on the specified platform
|
33
|
+
# @param [String] the ruby platform
|
21
34
|
def capture_core(platform)
|
22
35
|
core = case platform.downcase
|
23
36
|
when %r{darwin} then
|
@@ -26,14 +39,22 @@ module Terminitor
|
|
26
39
|
else
|
27
40
|
Terminitor::MacCapture
|
28
41
|
end
|
29
|
-
when %r{linux} then
|
42
|
+
when %r{linux} then
|
43
|
+
if not `which terminator`.chomp.empty?
|
44
|
+
Terminitor::TerminatorCapture
|
45
|
+
else
|
46
|
+
Terminitor::KonsoleCapture # TODO silly fallback, make better check
|
47
|
+
end
|
30
48
|
else nil
|
31
49
|
end
|
32
50
|
end
|
33
51
|
|
34
52
|
# Execute the core with the given method.
|
35
|
-
#
|
36
|
-
#
|
53
|
+
# @param [Symbol] symbol of method
|
54
|
+
# @param [String] Termfile name
|
55
|
+
# @example
|
56
|
+
# execute_core :process!, 'project'
|
57
|
+
# execute_core :setup!, 'my_project'
|
37
58
|
def execute_core(method, project)
|
38
59
|
if path = resolve_path(project)
|
39
60
|
core = find_core(RUBY_PLATFORM)
|
@@ -44,7 +65,10 @@ module Terminitor
|
|
44
65
|
end
|
45
66
|
|
46
67
|
# opens doc in system designated editor
|
47
|
-
#
|
68
|
+
# @param [String] path to termfile
|
69
|
+
# @param [String] editor
|
70
|
+
# @example
|
71
|
+
# open_in_editor '/path/to', 'nano'
|
48
72
|
def open_in_editor(path, editor=nil)
|
49
73
|
editor = editor || ENV['TERM_EDITOR'] || ENV['EDITOR']
|
50
74
|
say "please set $EDITOR or $TERM_EDITOR in your .bash_profile." unless editor
|
@@ -52,7 +76,8 @@ module Terminitor
|
|
52
76
|
end
|
53
77
|
|
54
78
|
# returns path to file
|
55
|
-
#
|
79
|
+
# @param [String] Termfile name
|
80
|
+
# @example resolve_path 'my_project'
|
56
81
|
def resolve_path(project)
|
57
82
|
unless project.empty?
|
58
83
|
path = config_path(project, :yml) # Give old yml path
|
@@ -68,26 +93,29 @@ module Terminitor
|
|
68
93
|
end
|
69
94
|
|
70
95
|
# returns first line of file
|
71
|
-
#
|
96
|
+
# @param [String] Termfile path
|
97
|
+
# @example grab_comment_for_file '/path/to'
|
72
98
|
def grab_comment_for_file(file)
|
73
99
|
first_line = File.readlines(file).first
|
74
100
|
first_line =~ /^\s*?#/ ? ("-" + first_line.gsub("#","")) : "\n"
|
75
101
|
end
|
76
102
|
|
77
103
|
# Return file in config_path
|
78
|
-
#
|
104
|
+
# @param [String] Termfile path
|
105
|
+
# @param [Symbol] Type of file
|
106
|
+
# @example config_path '/path/to', :term
|
79
107
|
def config_path(file, type = :yml)
|
80
108
|
return File.join(options[:root],"Termfile") if file.empty?
|
81
|
-
dir = File.join(ENV['HOME'],'.terminitor')
|
82
109
|
if type == :yml
|
83
|
-
File.join(
|
110
|
+
File.join(TERM_PATH, "#{file.sub(/\.yml$/, '')}.yml")
|
84
111
|
else
|
85
|
-
File.join(
|
112
|
+
File.join(TERM_PATH, "#{file.sub(/\.term$/, '')}.term")
|
86
113
|
end
|
87
114
|
end
|
88
115
|
|
89
116
|
# Returns error message depending if project is specified
|
90
|
-
#
|
117
|
+
# @param [String] Termfile name
|
118
|
+
# @example return_error_message 'hi
|
91
119
|
def return_error_message(project)
|
92
120
|
unless project.empty?
|
93
121
|
say "'#{project}' doesn't exist! Please run 'terminitor open #{project.gsub(/\..*/,'')}'"
|
@@ -99,6 +127,9 @@ module Terminitor
|
|
99
127
|
# This will clone a repo in the current directory.
|
100
128
|
# It will first try to clone via ssh(read/write),
|
101
129
|
# if not fall back to git-read only, else, fail.
|
130
|
+
# @param [String] Github username
|
131
|
+
# @param [String] Github project name
|
132
|
+
# @example github_clone 'achiu', 'terminitor'
|
102
133
|
def github_clone(username, project)
|
103
134
|
github = `which github`
|
104
135
|
return false if github.empty?
|
@@ -107,7 +138,10 @@ module Terminitor
|
|
107
138
|
end
|
108
139
|
|
109
140
|
# Fetch the git repo and run the setup block
|
110
|
-
#
|
141
|
+
# @param [String] Github username
|
142
|
+
# @param [String] Github project
|
143
|
+
# @param [Hash] options hash
|
144
|
+
# @example fetch_repo 'achiu', 'terminitor', :setup => true
|
111
145
|
def github_repo(username, project, options ={})
|
112
146
|
if github_clone(username, project)
|
113
147
|
path = File.join(Dir.pwd, project)
|
data/lib/terminitor/version.rb
CHANGED
data/lib/terminitor/yaml.rb
CHANGED
@@ -6,11 +6,13 @@ module Terminitor
|
|
6
6
|
attr_accessor :file
|
7
7
|
|
8
8
|
# Load in the Yaml file...
|
9
|
+
# @param [String] Path to termfile
|
9
10
|
def initialize(path)
|
10
11
|
@file = YAML.load File.read(path)
|
11
12
|
end
|
12
13
|
|
13
14
|
# Returns yaml file as Terminitor formmatted hash
|
15
|
+
# @return [Hash] Hash format of termfile
|
14
16
|
def to_hash
|
15
17
|
combined = @file.inject({}) do |base, item|
|
16
18
|
item = {item.keys.first => {:commands => item.values.first, :options => {}}}
|