heytmux 0.0.0 → 0.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cb566cb530c83ebd00f2dcfb707eb0e992593ee7
4
- data.tar.gz: a0a6b6a585a503d1d5ceab93752345b897c125b2
3
+ metadata.gz: 53af5daf1e2193c29b0a85affdb4fc72b739b536
4
+ data.tar.gz: 76fe05372f8e0252556ae26cbcb98f40579dca4c
5
5
  SHA512:
6
- metadata.gz: 3e768bf1f0f205cfe32546253338856ca6d0a6d6742cf8f6ab9244e146be6cf2978823114517cad0554a10ea94e7fbd65e8f54fdeda84a85ed04135a23b4632d
7
- data.tar.gz: 2df1f6fcff06149be16865c6991238519f5163bebaca3180c2a410a7dbf764edcadfd404c56b84642599d5cd335848d739037f3f7bc5f544d5bbc41a1decafc4
6
+ metadata.gz: 80b1a5981d257825a5ac9478c2e0dc089f4898a2ad17e49eb78d189227f3e46348fefe196b1b79b061237a28f1ee6730a5ccb5c22f7bf536d7f22cd37d64a0e8
7
+ data.tar.gz: b19c74aa98d105879a6374e9d92639906338d0dca952055c07d1af8f974421a69894d4d82e97c46136a68b8a6c209e4b107fddb2c4715e28655e16b752147529
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  Hey tmux!
2
2
  =========
3
3
 
4
+ [![travis-ci](https://travis-ci.org/junegunn/heytmux.svg?branch=master)](https://travis-ci.org/junegunn/heytmux) [![Coverage Status](https://coveralls.io/repos/github/junegunn/heytmux/badge.svg?branch=master)](https://coveralls.io/github/junegunn/heytmux?branch=master)
5
+
4
6
  Tmux scripting made easy.
5
7
 
6
8
  Installation
@@ -25,7 +27,9 @@ Plug 'junegunn/heytmux'
25
27
  ```
26
28
 
27
29
  - Registers `:Heytmux` command
28
- - No need to install Gem
30
+ - No need to install Gem if you only use Heytmux inside Vim
31
+ - But if you want it to be globally available,
32
+ `Plug 'junegunn/heytmux', { 'do': 'gem install heytmux' }`
29
33
 
30
34
  Usage
31
35
  -----
@@ -252,7 +256,7 @@ following example will not work as expected.
252
256
  ```
253
257
 
254
258
  With `expect` construct, you can make Heytmux wait until a certain regular
255
- expression pattern appears on the the pane (a la [Expect][expect]).
259
+ expression pattern appears on the pane (a la [Expect][expect]).
256
260
 
257
261
  [expect]: https://en.wikipedia.org/wiki/Expect
258
262
 
@@ -265,6 +269,23 @@ expression pattern appears on the the pane (a la [Expect][expect]).
265
269
  - uptime
266
270
  ```
267
271
 
272
+ #### Special commands
273
+
274
+ In addition to `expect`, Heytmux also supports `sleep` and `keys` commands.
275
+ `sleep` suspends the execution for a given time period. It's useful when the
276
+ shell on the target pane is non-interactive so you can't send `sleep` command
277
+ to it. `keys` command is for sending special keys, such as `c-c` (CTRL-C)
278
+ using `tmux send-keys` command. To send multiple keys, specify the keys as
279
+ a YAML list (e.g. `[c-c, c-l]`).
280
+
281
+ ```yaml
282
+ - servers:
283
+ - server 1:
284
+ - vmstat 2 | tee log
285
+ - sleep: 3
286
+ - keys: c-c
287
+ ```
288
+
268
289
  Vim plugin
269
290
  ----------
270
291
 
@@ -13,8 +13,6 @@ module Heytmux
13
13
  'pane-border-format' => '#{pane_title}'
14
14
  }.freeze
15
15
 
16
- SUPPORTED_ACTIONS = %w[expect].to_set.freeze
17
-
18
16
  WINDOWS_KEY = 'windows'
19
17
  LAYOUT_KEY = 'layout'
20
18
  PANES_KEY = 'panes'
@@ -28,5 +26,10 @@ end
28
26
 
29
27
  require 'heytmux/version'
30
28
  require 'heytmux/validations'
29
+ require 'heytmux/pane_action'
30
+ require 'heytmux/pane_actions/paste'
31
+ require 'heytmux/pane_actions/expect'
32
+ require 'heytmux/pane_actions/sleep'
33
+ require 'heytmux/pane_actions/keys'
31
34
  require 'heytmux/tmux'
32
35
  require 'heytmux/core'
@@ -69,33 +69,18 @@ module Heytmux
69
69
  end
70
70
  end
71
71
 
72
+ # Finds appropriate PaneActions for the commands and processes them
72
73
  def process_command!(window_index, pane)
73
74
  pane_index, item, commands = pane.values_at(:index, :item, :command)
74
- [*commands].each do |command|
75
- case command
76
- when Hash
77
- _action, regex = command.first
78
- regex = Regexp.compile(regex.to_s)
79
- wait_until do
80
- content = Tmux.capture(window_index, pane_index)
81
- content =~ regex
82
- end
83
- else
84
- command = command.gsub(ITEM_PAT, item) if item
85
- Tmux.paste(window_index, pane_index, command)
75
+ [*commands].compact.each do |command|
76
+ label, body = command.is_a?(Hash) ? command.first : [:paste, command]
77
+ PaneAction.for(label).process(window_index, pane_index, body) do |source|
78
+ string = source.to_s
79
+ item ? string.gsub(ITEM_PAT, item) : string
86
80
  end
87
81
  end
88
82
  end
89
83
 
90
- def wait_until
91
- timeout = Time.now + EXPECT_TIMEOUT
92
- loop do
93
- sleep EXPECT_SLEEP_INTERVAL
94
- return if yield
95
- raise 'Timed out' if Time.now > timeout
96
- end
97
- end
98
-
99
84
  # Groups entities by the group key and extracts unique, sorted indexes and
100
85
  # returns stateful indexer that issues index number for the given group key
101
86
  def indexer(entities, group_key, index_key)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heytmux
4
+ # Base class for pane actions. To implement a new type of action that is
5
+ # applied to a designated pane, one should write a class that derives from
6
+ # this class. Use register class method to associate the class with one or
7
+ # more labels.
8
+ class PaneAction
9
+ # Validation method to be overridden. Throw ArgumentError if body is
10
+ # invalid.
11
+ def validate(_body)
12
+ nil
13
+ end
14
+
15
+ # Defines the behavior of the action. Should be implemented by the
16
+ # subclasses. When the method is called, a block is passed that is for
17
+ # replacing {{ item }} in the body. One may or may not want to use it on
18
+ # body depending on the use case.
19
+ def process(_window_index, _pane_index, _body)
20
+ raise NotImplementedError
21
+ end
22
+
23
+ class << self
24
+ # Registers a PaneAction class with the label
25
+ def register(*labels, klass)
26
+ instance = klass.new
27
+ (@actions ||= {}).merge!(
28
+ Hash[labels.map { |label| [label, instance] }]
29
+ )
30
+ end
31
+
32
+ # Finds PaneAction class for the label
33
+ def for(label)
34
+ @actions[label.to_sym]
35
+ end
36
+
37
+ def inherited(klass)
38
+ def klass.register(*labels)
39
+ PaneAction.register(*labels, self)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module Heytmux
6
+ module PaneActions
7
+ # Pastes the command onto the pane
8
+ class Expect < PaneAction
9
+ register :expect
10
+
11
+ def validate(body)
12
+ raise ArgumentError, 'Expect condition is empty' if body.to_s.empty?
13
+ end
14
+
15
+ def process(window_index, pane_index, body)
16
+ regex = Regexp.compile(yield(body))
17
+ wait_until do
18
+ content = Tmux.capture(window_index, pane_index)
19
+ raise 'Failed to capture pane content' unless $CHILD_STATUS.success?
20
+ content =~ regex
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def wait_until
27
+ timeout = Time.now + EXPECT_TIMEOUT
28
+ loop do
29
+ sleep EXPECT_SLEEP_INTERVAL
30
+ return if yield
31
+ raise 'Timed out' if Time.now > timeout
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heytmux
4
+ module PaneActions
5
+ # Send keys using tmux send-keys command
6
+ class Keys < PaneAction
7
+ register :keys, :key
8
+
9
+ def validate(body)
10
+ body = body.is_a?(Array) ? body : [body]
11
+ body.each do |key|
12
+ unless Validations.valid_string?(key)
13
+ raise ArgumentError, 'Keys must be given as a string or a list'
14
+ end
15
+ end
16
+ end
17
+
18
+ def process(window_index, pane_index, body)
19
+ Tmux.tmux('send-keys', Tmux.target(window_index, pane_index), body)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heytmux
4
+ module PaneActions
5
+ # Waits for a certain regular expression pattern appears
6
+ class Paste < PaneAction
7
+ register :paste
8
+
9
+ def validate(body)
10
+ case body
11
+ when Hash, Array
12
+ raise ArgumentError, "Invalid command: #{body}"
13
+ end
14
+ end
15
+
16
+ def process(window_index, pane_index, body)
17
+ Tmux.paste(window_index, pane_index, yield(body))
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Heytmux
4
+ module PaneActions
5
+ # Sleeps for the given duration
6
+ class Sleep < PaneAction
7
+ register :sleep
8
+
9
+ def validate(body)
10
+ raise ArgumentError, 'Sleep expects positive number' if body.to_f <= 0
11
+ end
12
+
13
+ def process(_window_index, _pane_index, body)
14
+ sleep(body.to_f)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -65,29 +65,28 @@ module Heytmux
65
65
  # - command_string
66
66
  # - [command_strings_or_hashes...]
67
67
  def validate_commands(commands)
68
- return if commands.is_a?(String)
69
68
  message = "Invalid command: #{commands.inspect}"
70
- case commands
71
- when Array
72
- hashes, strings = commands.partition { |command| command.is_a?(Hash) }
73
- hashes.each do |command|
69
+ commands = commands.is_a?(Array) ? commands : [commands]
70
+ commands.each do |command|
71
+ case command
72
+ when Hash
74
73
  raise ArgumentError, message unless single_spec?(command)
75
- action, condition = command.first
76
- unless SUPPORTED_ACTIONS.include?(action)
77
- raise ArgumentError, "Unsupported action: #{action}"
78
- end
79
- raise ArgumentError, message unless valid_name?(condition)
80
- Regexp.compile(condition.to_s)
74
+ label, body = command.first
75
+ validate_action(label, body)
76
+ else
77
+ raise ArgumentError, message unless valid_string?(command)
81
78
  end
82
- strings.each do |command|
83
- raise ArgumentError, message unless valid_name?(command)
84
- end
85
- else
86
- raise ArgumentError, message unless valid_name?(commands)
87
79
  end
88
80
  nil
89
81
  end
90
82
 
83
+ # Checks if the action is currently supported
84
+ def validate_action(label, body)
85
+ action = PaneAction.for(label)
86
+ raise ArgumentError, "Unsupported action: #{label}" unless action
87
+ action.validate(body)
88
+ end
89
+
91
90
  # Checks if the given hash only contains one mapping from a string to
92
91
  # a hash
93
92
  def single_spec?(spec, *klasses)
@@ -95,11 +94,13 @@ module Heytmux
95
94
  second.nil? && !key.to_s.empty? &&
96
95
  (klasses.empty? || klasses.any? { |k| value.is_a?(k) })
97
96
  end
98
- private_class_method :single_spec?
97
+
98
+ def valid_string?(value)
99
+ !(value.is_a?(Array) || value.is_a?(Hash))
100
+ end
99
101
 
100
102
  def valid_name?(value)
101
- !(value.nil? || value.is_a?(Array) || value.to_s.empty?)
103
+ valid_string?(value) && !value.to_s.empty?
102
104
  end
103
- private_class_method :valid_name?
104
105
  end
105
106
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Heytmux
4
- VERSION = '0.0.0'
4
+ VERSION = '0.1.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heytmux
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Junegunn Choi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-07-03 00:00:00.000000000 Z
11
+ date: 2017-07-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -110,6 +110,11 @@ files:
110
110
  - heytmux.gemspec
111
111
  - lib/heytmux.rb
112
112
  - lib/heytmux/core.rb
113
+ - lib/heytmux/pane_action.rb
114
+ - lib/heytmux/pane_actions/expect.rb
115
+ - lib/heytmux/pane_actions/keys.rb
116
+ - lib/heytmux/pane_actions/paste.rb
117
+ - lib/heytmux/pane_actions/sleep.rb
113
118
  - lib/heytmux/tmux.rb
114
119
  - lib/heytmux/validations.rb
115
120
  - lib/heytmux/version.rb
@@ -132,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
137
  version: '0'
133
138
  requirements: []
134
139
  rubyforge_project:
135
- rubygems_version: 2.6.8
140
+ rubygems_version: 2.6.11
136
141
  signing_key:
137
142
  specification_version: 4
138
143
  summary: Hey tmux!