terminitor 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 => {}}}
|