mani 0.0.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b269a1ad0554dc57959f8142fe75d121b9bf472
4
+ data.tar.gz: 76ead7572bb2b1b3ce1d05def8a96b54acad42e9
5
+ SHA512:
6
+ metadata.gz: 18e6bbc6919d0d1744b3894b9ea948dbdfc652befde15b8561c450404978d34741d69c53fceb4aca2e5391f8da597ce8c4b9fead94d05fe99ac35a50930bb6a4
7
+ data.tar.gz: e3b81f2cbc77d58f183414f867ed51389db569ef6809c9f189ec52aa21c7f5bda1711f860d4143e2ca10a91250f380cd77d99fb584a20fbecb28153113e48747
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ /*.gem
2
+ /*.rbc
3
+ /.bundle
4
+ /.config
5
+ /coverage
6
+ /InstalledFiles
7
+ /lib/bundler/man
8
+ /pkg
9
+ /rdoc
10
+ /spec/reports
11
+ /test/tmp
12
+ /test/version_tmp
13
+ /tmp
14
+
15
+ # YARD artifacts
16
+ /.yardoc
17
+ /_yardoc
18
+ /doc/
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ Metrics/CyclomaticComplexity:
2
+ Max: 8
3
+
4
+ Metrics/PerceivedComplexity:
5
+ Max: 8
6
+
7
+ Metrics/MethodLength:
8
+ Max: 18
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'x_do'
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (4.1.6)
5
+ i18n (~> 0.6, >= 0.6.9)
6
+ json (~> 1.7, >= 1.7.7)
7
+ minitest (~> 5.1)
8
+ thread_safe (~> 0.1)
9
+ tzinfo (~> 1.1)
10
+ addressable (2.3.6)
11
+ builder (3.2.2)
12
+ descendants_tracker (0.0.4)
13
+ thread_safe (~> 0.3, >= 0.3.1)
14
+ docile (1.1.5)
15
+ faraday (0.9.0)
16
+ multipart-post (>= 1.2, < 3)
17
+ git (1.2.8)
18
+ github_api (0.12.1)
19
+ addressable (~> 2.3)
20
+ descendants_tracker (~> 0.0.4)
21
+ faraday (~> 0.8, < 0.10)
22
+ hashie (>= 3.2)
23
+ multi_json (>= 1.7.5, < 2.0)
24
+ nokogiri (~> 1.6.3)
25
+ oauth2
26
+ hashie (3.3.1)
27
+ highline (1.6.21)
28
+ i18n (0.6.11)
29
+ jeweler (2.0.1)
30
+ builder
31
+ bundler (>= 1.0)
32
+ git (>= 1.2.5)
33
+ github_api
34
+ highline (>= 1.6.15)
35
+ nokogiri (>= 1.5.10)
36
+ rake
37
+ rdoc
38
+ json (1.8.1)
39
+ jwt (1.0.0)
40
+ mini_portile (0.6.0)
41
+ minitest (5.4.2)
42
+ multi_json (1.10.1)
43
+ multi_xml (0.5.5)
44
+ multipart-post (2.0.0)
45
+ nokogiri (1.6.3.1)
46
+ mini_portile (= 0.6.0)
47
+ oauth2 (1.0.0)
48
+ faraday (>= 0.8, < 0.10)
49
+ jwt (~> 1.0)
50
+ multi_json (~> 1.3)
51
+ multi_xml (~> 0.5)
52
+ rack (~> 1.2)
53
+ rack (1.5.2)
54
+ rake (10.3.2)
55
+ rdoc (3.12.2)
56
+ json (~> 1.4)
57
+ shoulda (3.5.0)
58
+ shoulda-context (~> 1.0, >= 1.0.1)
59
+ shoulda-matchers (>= 1.4.1, < 3.0)
60
+ shoulda-context (1.2.1)
61
+ shoulda-matchers (2.7.0)
62
+ activesupport (>= 3.0.0)
63
+ simplecov (0.9.1)
64
+ docile (~> 1.1.0)
65
+ multi_json (~> 1.0)
66
+ simplecov-html (~> 0.8.0)
67
+ simplecov-html (0.8.0)
68
+ thread_safe (0.3.4)
69
+ tzinfo (1.2.2)
70
+ thread_safe (~> 0.1)
71
+
72
+ PLATFORMS
73
+ ruby
74
+
75
+ DEPENDENCIES
76
+ bundler (~> 1.0)
77
+ jeweler (~> 2.0.1)
78
+ rdoc (~> 3.12)
79
+ shoulda
80
+ simplecov
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2014, Nick Sinopoli <nsinopoli@gmail.com> All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions are met:
5
+
6
+ * Redistributions of source code must retain the above copyright notice, this
7
+ list of conditions and the following disclaimer.
8
+
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+
13
+ * Neither the name of Nick Sinopoli nor the names of his contributors may be
14
+ used to endorse or promote products derived from this software without
15
+ specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/Makefile ADDED
@@ -0,0 +1,12 @@
1
+ all: style test
2
+
3
+ install:
4
+ gem install rubocop
5
+
6
+ style:
7
+ rubocop lib
8
+
9
+ test:
10
+ ruby spec.rb
11
+
12
+ .PHONY: test
data/README.md ADDED
@@ -0,0 +1,318 @@
1
+ # Mani
2
+
3
+ **Mani** is a window automation tool. Common tasks (such as launching programs,
4
+ visiting websites, and typing text) can be scripted using a simple Ruby DSL.
5
+
6
+ While [xmonad](http://xmonad.org/) is currently the only supported window
7
+ manager, Mani can support any window manager for the X Window System with just
8
+ a few lines of code. Please open an
9
+ [issue](https://github.com/NSinopoli/mani/issues) to add support for your
10
+ favorite window manager.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ gem install mani
16
+ ```
17
+
18
+ ### xmonad
19
+
20
+ You'll have to do a few things to get Mani to work as expected under xmonad.
21
+
22
+ 1. Install [xdotool](http://www.semicomplete.com/projects/xdotool/).
23
+ 2. Add [XMonad.Hooks.EwmhDesktops](http://xmonad.org/xmonad-docs/xmonad-contrib/XMonad-Hooks-EwmhDesktops.html)
24
+ to your xmonad configuration, and then restart xmonad.
25
+
26
+ ```haskell
27
+ import XMonad.Hooks.EwmhDesktops
28
+
29
+ main = xmonad $ ewmh defaultConfig{ handleEventHook =
30
+ handleEventHook defaultConfig <+> fullscreenEventHook }
31
+ ```
32
+
33
+ ## Getting Started
34
+
35
+ Here's a simple example:
36
+
37
+ ```ruby
38
+ # hello.rb
39
+ Mani.new(window_manager: :xmonad) do
40
+ window :hello, launch: 'urxvt' do
41
+ run 'echo "Hello, world."'
42
+ end
43
+ end
44
+ ```
45
+
46
+ Run it:
47
+
48
+ ```bash
49
+ $ mani hello.rb
50
+ ```
51
+
52
+ Provided you have
53
+ [rxvt-unicode](http://software.schmorp.de/pkg/rxvt-unicode.html) installed, a
54
+ new terminal will open and the command `echo "Hello, world."` will run inside
55
+ it.
56
+
57
+ ## Examples
58
+
59
+ Check out the [examples](examples) directory for more examples.
60
+
61
+ ## DSL Overview
62
+
63
+ `Mani.new` should be at the beginning of every script. It takes an options
64
+ hash, which can take the following keys:
65
+
66
+ * `:window_manager` - the window manager (currently only `:xmonad` is supported)
67
+ * `:switch_to_workspace` - an optional proc which, when called, returns a
68
+ sequence which, when interpreted, will switch to the specified workspace
69
+ (defaults to the default sequence used by the `:window_manager` to change
70
+ workspaces)
71
+
72
+ ```ruby
73
+ switcher = ->(space) { "super+#{space}" }
74
+ Mani.new(window_manager: :xmonad, switch_to_workspace: switcher) do
75
+ # Switch to workspace 1
76
+ workspace 1
77
+
78
+ # Switch to workspace 2
79
+ workspace 2
80
+ end
81
+ ```
82
+
83
+ `workspace` switches to the specified workspace. If a block is supplied, it
84
+ will be called.
85
+
86
+ ```ruby
87
+ Mani.new(window_manager: :xmonad) do
88
+ # Switch to workspace 1, launch a terminal and run 'echo "Hello, world."'
89
+ workspace 1 do
90
+ window :hello, launch: 'urxvt' do
91
+ run 'echo "Hello, world."'
92
+ end
93
+ end
94
+
95
+ # Switch to workspace 2
96
+ workspace 2
97
+ end
98
+ ```
99
+
100
+ `window` serves as the container for window commands. Its first argument is the
101
+ window name. Its second (optional) argument is an options hash, which can take
102
+ the following keys:
103
+
104
+ * `:launch` - the program to be launched
105
+ * `:delay` - the amount of time, in seconds, to delay after launching the
106
+ program (defaults to 0.5)
107
+
108
+ If a block is supplied, it will be called.
109
+
110
+ ```ruby
111
+ Mani.new(window_manager: :xmonad) do
112
+ # Create a new terminal window, wait one second, and then run 'ls'
113
+ window :ls do
114
+ launch 'urxvt', delay: 1 do
115
+ run 'ls'
116
+ end
117
+ end
118
+
119
+ # The same process can be accomplished using this condensed syntax:
120
+ window :ls, launch: 'urxvt', delay: 1 do
121
+ run 'ls'
122
+ end
123
+
124
+ # Just launch a program
125
+ window :thunderbird, launch: 'thunderbird'
126
+
127
+ # Return to the :ls window, and then run 'date'
128
+ window :ls do
129
+ run 'date'
130
+ end
131
+ end
132
+ ```
133
+
134
+ `launch` launches a program. It takes an optional options hash, which can take
135
+ the following keys:
136
+
137
+ * `:delay` - the amount of time, in seconds, to delay after launching the
138
+ program (defaults to 0.5)
139
+
140
+ ```ruby
141
+ Mani.new(window_manager: :xmonad) do
142
+ # Create a new terminal window, wait one second, and then run 'ls'
143
+ window :ls do
144
+ launch 'urxvt', delay: 1 do
145
+ run 'ls'
146
+ end
147
+ end
148
+ end
149
+ ```
150
+
151
+ `run` executes a [combination](#combination-syntax) within a window. It takes
152
+ an optional options hash, which can take the following keys:
153
+
154
+ * `:delay` - the amount of time, in seconds, to delay after running the
155
+ command (defaults to 0.5)
156
+
157
+ ```ruby
158
+ Mani.new(window_manager: :xmonad) do
159
+ # Run 'ls'
160
+ window :ls, launch: 'urxvt', delay: 1 do
161
+ run 'ls'
162
+ end
163
+ end
164
+ ```
165
+
166
+ `type` types a [combination](#combination-syntax) within a window. It takes an
167
+ optional options hash, which can take the following keys:
168
+
169
+ * `:delay` - the amount of time, in seconds, to delay after typing the text
170
+ (defaults to 0.5)
171
+
172
+ ```ruby
173
+ Mani.new(window_manager: :xmonad) do
174
+ # Launch gvim
175
+ window :gvim, launch: 'gvim -f', delay: 2 do
176
+ # Wait two seconds, then move to the end of the buffer, start a new line,
177
+ # and type "Hello, world."
178
+ type 'GoHello, world.'
179
+ end
180
+ end
181
+ ```
182
+
183
+ `visit` is used to visit a website. It takes an optional options hash, which
184
+ can take the following keys:
185
+
186
+ * `:delay` - the amount of time, in seconds, to delay after entering the url
187
+ (defaults to 0.5)
188
+
189
+ Note that depending on the browser, you may need to insert an "F6"
190
+ [keystroke](#combination-syntax) at the beginning of the url.
191
+
192
+ ```ruby
193
+ Mani.new(window_manager: :xmonad) do
194
+ window :chromium, launch: 'chromium', delay: 1.5 do
195
+ visit 'localhost:8080'
196
+ end
197
+
198
+ window :firefox, launch: 'firefox', delay: 1.5 do
199
+ # Type "F6" first so that the cursor is in the address bar before typing
200
+ # the url.
201
+ visit '{{F6}}gmail.com'
202
+ end
203
+ end
204
+ ```
205
+
206
+ `browser_tab` is used to either create a new browser tab or switch to a
207
+ specific tab. Its first argument, when `:new`, will create a new tab. If it is
208
+ an integer, the browser will instead switch to that tab. Its second argument
209
+ is an optional options hash, which can take the following keys:
210
+
211
+ * `:delay` - the amount of time, in seconds, to delay after handling the tab
212
+ (defaults to 0.5)
213
+
214
+ If a block is supplied, it will be called.
215
+
216
+ ```ruby
217
+ Mani.new(window_manager: :xmonad) do
218
+ window :chromium, launch: 'chromium', delay: 1.5 do
219
+ # Visit 'localhost:8080' within the initial tab
220
+ visit 'localhost:8080'
221
+
222
+ # Create a new tab and visit 'news.ycombinator.com'
223
+ browser_tab :new do
224
+ visit 'news.ycombinator.com'
225
+ end
226
+
227
+ # Switch back to the first tab (localhost:8080)
228
+ browser_tab 1
229
+ end
230
+ end
231
+ ```
232
+
233
+ ### Combination Syntax
234
+
235
+ Simulating keystrokes for function keys and modifiers requires the use of a
236
+ special syntax. The `{{` and `}}` tags are used to indicate a sequence.
237
+ Chording is achieved by separating keys with a `+` sign.
238
+
239
+ ```ruby
240
+ Mani.new(window_manager: :xmonad) do
241
+ window :firefox, launch: 'firefox', delay: 1.5 do
242
+ # Type "F6" first so that the cursor is in the address bar before typing
243
+ # the url.
244
+ visit '{{F6}}gmail.com'
245
+
246
+ # Paste the contents of the clipboard into the url.
247
+ visit 'localhost:8080/reports/{{ctrl+v}}/preview'
248
+ end
249
+ end
250
+ ```
251
+
252
+ In the event a literal `{{` or `}}` is desired, a `%` sign can be added as a
253
+ prefix to escape the sequence.
254
+
255
+ ```ruby
256
+ Mani.new(window_manager: :xmonad) do
257
+ window :hello, launch: 'urxvt' do
258
+ run 'echo "%{{literal opening brackets"'
259
+ run 'echo "literal closing brackets%}}"'
260
+ run 'echo "%{{literal opening and closing brackets%}}"'
261
+ end
262
+ end
263
+ ```
264
+
265
+ ## Contributing
266
+
267
+ Please follow the guidelines below.
268
+
269
+ ### Issue Reporting
270
+
271
+ * Use the [issue tracker](https://github.com/NSinopoli/mani/issues) to report
272
+ any issues or ideas for improvements.
273
+ * Check that the issue has not already been reported.
274
+ * Check that the issue has not already been fixed in the latest code (i.e., on
275
+ `master`).
276
+ * Be clear, concise, and precise in your description of the problem.
277
+ * Open an issue with a descriptive title and a summary in grammatically
278
+ correct, complete sentences.
279
+ * Include any relevant code to the issue summary.
280
+
281
+ ### Pull Requests
282
+
283
+ * Read [how to properly contribute to open source projects on Github](http://gun.io/blog/how-to-github-fork-branch-and-pull-request).
284
+ * Fork the project.
285
+ * Use a topic/feature branch to easily amend a pull request later, if
286
+ necessary.
287
+ * Write [good commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
288
+ * Use the same [coding conventions](#code-conventions) as the rest of the
289
+ project.
290
+ * Commit and push until you are happy with your contribution.
291
+ * Make sure to add [tests](#tests) for it. This is important so I don't break
292
+ it in a future version unintentionally.
293
+ * Please try not to mess with the version or history. If you want to have your
294
+ own version, please isolate the change to its own commit so I can cherry-pick
295
+ around it.
296
+ * [Squash related commits together](http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html).
297
+ * Open a [pull request](https://help.github.com/articles/using-pull-requests)
298
+ that relates to *only* one subject with a clear title and description in
299
+ grammatically correct, complete sentences.
300
+
301
+ ### Code Conventions
302
+
303
+ **Mani** follows the conventions laid out in the
304
+ [Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide). These
305
+ conventions are enforced by [RuboCop](https://github.com/bbatsov/rubocop).
306
+
307
+ ```
308
+ make install
309
+ make style
310
+ ```
311
+
312
+ Please ensure that any contributed code does not produce any RuboCop offenses.
313
+
314
+ ### Tests
315
+
316
+ ```
317
+ make test
318
+ ```
data/bin/mani ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.dirname(File.realpath(__FILE__)) + '/../lib')
4
+
5
+ require 'mani'
6
+ require 'mani/version'
7
+
8
+ require 'optparse'
9
+
10
+ class Mani
11
+ class CLI
12
+ # Parses command line options.
13
+ #
14
+ # @param [Array] args The command line arguments
15
+ def self.parse_options(args)
16
+ option_parser = OptionParser.new do |opts|
17
+ opts.banner = "Usage: mani FILE"
18
+
19
+ opts.separator ""
20
+
21
+ opts.separator "Example:"
22
+ opts.separator " $ mani workspace_initializer.rb"
23
+
24
+ opts.separator ""
25
+
26
+ opts.separator "Options:"
27
+
28
+ opts.on("-v", "--version", "Print the version") do |v|
29
+ puts "Mani v#{Mani::Version::VERSION}"
30
+ exit
31
+ end
32
+
33
+ opts.on_tail("-h", "--help", "Show this message") do
34
+ puts opts
35
+ exit
36
+ end
37
+ end
38
+
39
+ option_parser.parse!(args)
40
+ end
41
+ end
42
+ end
43
+
44
+ ARGV << '-h' if ARGV.empty? && $stdin.tty?
45
+
46
+ Mani::CLI.parse_options(ARGV) if $stdin.tty?
47
+
48
+ load ARGF.file
@@ -0,0 +1,17 @@
1
+ Mani.new do
2
+ window :urxvt, launch: 'urxvt' do
3
+ # Copy the most recent report id
4
+ run %q(
5
+ ssh 127.0.0.1 -p 49154
6
+ psql -h localhost -U web_dev web_development <<<
7
+ 'SELECT id FROM reports order by id desc limit 1;'
8
+ | grep -m 1 \[0-9\] | xclip -selection c
9
+ ).strip.gsub(/\s+/, ' ')
10
+ run 'exit'
11
+ end
12
+
13
+ window :chrome, launch: 'google-chrome-stable', delay: 1 do
14
+ # Paste the report id within the url
15
+ visit 'localhost:8080/reports/{{ctrl+v}}/preview'
16
+ end
17
+ end
data/examples/hello.rb ADDED
@@ -0,0 +1,5 @@
1
+ Mani.new(window_manager: :xmonad) do
2
+ window :hello, launch: 'urxvt' do
3
+ run 'echo "Hello, world."'
4
+ end
5
+ end
@@ -0,0 +1,46 @@
1
+ Mani.new(window_manager: :xmonad) do
2
+ workspace 1 do
3
+ window :top, launch: 'urxvt' do
4
+ run 'top'
5
+ end
6
+
7
+ window :date, launch: 'urxvt' do
8
+ run 'date'
9
+ end
10
+ end
11
+
12
+ workspace 2 do
13
+ window :firefox, launch: 'firefox', delay: 1.5 do
14
+ # Type "F6" first so that the cursor is in the address bar before typing
15
+ # the url.
16
+ visit '{{F6}}gmail.com'
17
+ end
18
+ end
19
+
20
+ workspace 3 do
21
+ # Use "-f" to prevent vim from forking and detaching from the original
22
+ # process.
23
+ window :gvim, launch: 'gvim -f', delay: 2 do
24
+ type 'GoHello, world.'
25
+ end
26
+ end
27
+
28
+ workspace 4 do
29
+ window :thunderbird, launch: 'thunderbird'
30
+
31
+ window :hipchat, launch: 'hipchat', delay: 3
32
+ end
33
+
34
+ workspace 8 do
35
+ window :chromium, launch: 'chromium', delay: 1.5 do
36
+ visit 'localhost:8080'
37
+
38
+ browser_tab :new do
39
+ visit 'news.ycombinator.com'
40
+ end
41
+
42
+ # Switch back to the first tab (localhost)
43
+ browser_tab 1
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,88 @@
1
+ require 'strscan'
2
+
3
+ class Mani
4
+ # This class contains methods to handle the tokenization of strings.
5
+ class Tokenizer
6
+ # The escape character
7
+ ESCAPE_CHARACTER = '%'
8
+
9
+ # The delimiter signifying the start of a sequence
10
+ SEQUENCE_OPEN_DELIMITER = '{{'
11
+
12
+ # The delimiter signifying the end of a sequence
13
+ SEQUENCE_CLOSE_DELIMITER = '}}'
14
+
15
+ # The delimiter signifying an "open sequence" escape sequence
16
+ LITERAL_OPEN_DELIMITER = ESCAPE_CHARACTER + SEQUENCE_OPEN_DELIMITER
17
+
18
+ # The delimiter signifying a "close sequence" escape sequence
19
+ LITERAL_CLOSE_DELIMITER = ESCAPE_CHARACTER + SEQUENCE_CLOSE_DELIMITER
20
+
21
+ # The pattern to match the start of a sequence
22
+ SEQUENCE_OPEN = /
23
+ # find opening delimiter at beginning of string...
24
+ ^#{SEQUENCE_OPEN_DELIMITER}
25
+ # ...or elsewhere in the string, provided it's not preceded by
26
+ # ESCAPE_CHARACTER
27
+ |[^#{ESCAPE_CHARACTER}]#{SEQUENCE_OPEN_DELIMITER}
28
+ /x
29
+
30
+ # The pattern to match the end of a sequence
31
+ SEQUENCE_CLOSE = /
32
+ # find closing delimiter at beginning of string...
33
+ ^#{SEQUENCE_CLOSE_DELIMITER}
34
+ # ...or elsewhere in the string, provided it's not preceded by
35
+ # ESCAPE_CHARACTER
36
+ |[^#{ESCAPE_CHARACTER}]#{SEQUENCE_CLOSE_DELIMITER}
37
+ /x
38
+
39
+ # Retrieves the tokens comprising the supplied text.
40
+ #
41
+ # @param [String] text The text
42
+ # @return [Array]
43
+ def self.get_tokens(text)
44
+ tokenize StringScanner.new(text), []
45
+ end
46
+
47
+ # Strips the comment delimiters from the supplied text.
48
+ #
49
+ # @param [String] text The text
50
+ # @return [String]
51
+ def self.strip_comment_delimiters(text)
52
+ text
53
+ .gsub(LITERAL_OPEN_DELIMITER, SEQUENCE_OPEN_DELIMITER)
54
+ .gsub(LITERAL_CLOSE_DELIMITER, SEQUENCE_CLOSE_DELIMITER)
55
+ end
56
+
57
+ # Recursively scans the string within the supplied scanner to produce a
58
+ # list of tokens.
59
+ #
60
+ # @param [StringScanner] scanner The string scanner
61
+ # @param [Array] tokens The tokens
62
+ # @return [Array]
63
+ def self.tokenize(scanner, tokens)
64
+ match = scanner.scan_until SEQUENCE_OPEN
65
+ unless match
66
+ static = strip_comment_delimiters scanner.rest
67
+ tokens.concat [[:static, static]] unless static.empty?
68
+ return tokens
69
+ end
70
+
71
+ if !scanner.check_until SEQUENCE_CLOSE
72
+ static = strip_comment_delimiters(match + scanner.rest)
73
+ tokens.concat [[:static, static]]
74
+ else
75
+ static = strip_comment_delimiters match.chomp(SEQUENCE_OPEN_DELIMITER)
76
+ tokens.concat [[:static, static]] unless static.empty?
77
+
78
+ match = scanner.scan_until SEQUENCE_CLOSE
79
+ match.chomp! SEQUENCE_CLOSE_DELIMITER
80
+
81
+ sequence = strip_comment_delimiters match
82
+ tokens.concat [[:sequence, sequence]] unless sequence.empty?
83
+
84
+ tokenize scanner, tokens
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,7 @@
1
+ class Mani
2
+ # This class holds the Mani version information.
3
+ class Version
4
+ # The current version
5
+ VERSION = '0.0.2'
6
+ end
7
+ end
@@ -0,0 +1,95 @@
1
+ class Mani
2
+ # This class contains methods to handle operations run within windows.
3
+ class Window
4
+ # Creates a new tab, or switches to the supplied tab.
5
+ #
6
+ # @param [Symbol, Integer] tab The tab. If :new is specified, a new tab
7
+ # will be created and the window will switch to it. If an integer is
8
+ # specified, the window will switch to that (previously existing) tab.
9
+ # @param [Hash] options The options
10
+ # * :delay (Numeric) _Optional_ The amount of time, in seconds, to delay
11
+ # after handling the tab (defaults to 0.5)
12
+ # @param [Proc] block The code to execute after handling the tab
13
+ def browser_tab(tab, options = {}, &block)
14
+ @windowing_system.focus_window @pid
15
+
16
+ if tab == :new
17
+ @windowing_system.type_keysequence 'ctrl+t'
18
+ @windowing_system.type_keysequence 'alt+9'
19
+ elsif tab >= 1 && tab <= 8
20
+ @windowing_system.type_keysequence "alt+#{tab}"
21
+ elsif tab > 8
22
+ @windowing_system.type_keysequence 'alt+8'
23
+ (tab - 8).times { @windowing_system.type_keysequence 'ctrl+Tab' }
24
+ else
25
+ return
26
+ end
27
+
28
+ sleep options[:delay] || 0.5
29
+
30
+ instance_eval(&block) if block
31
+ end
32
+
33
+ # Initializes the window.
34
+ #
35
+ # @param [Object] windowing_system The windowing system
36
+ def initialize(windowing_system)
37
+ @windowing_system = windowing_system
38
+ end
39
+
40
+ # Launches the supplied program.
41
+ #
42
+ # @param [String] program The program
43
+ # @param [Hash] options The options
44
+ # * :delay (Numeric) _Optional_ The amount of time, in seconds, to delay
45
+ # after launching the program (defaults to 0.5)
46
+ # @param [Proc] block The code to execute after launching the program
47
+ def launch(program, options = {}, &block)
48
+ return if @pid
49
+
50
+ @pid = Process.spawn program
51
+ Process.detach @pid
52
+
53
+ sleep options[:delay] || 0.5
54
+
55
+ instance_eval(&block) if block
56
+ end
57
+
58
+ # Runs the supplied command within the current window.
59
+ #
60
+ # @param [String] command The command
61
+ # @param [Hash] options The options
62
+ # * :delay (Numeric) _Optional_ The amount of time, in seconds, to delay
63
+ # after running the command (defaults to 0.5)
64
+ def run(command, options = {})
65
+ @windowing_system.focus_window @pid
66
+ @windowing_system.type_combination command
67
+ @windowing_system.type_keysequence 'Return'
68
+
69
+ sleep options[:delay] || 0.5
70
+ end
71
+
72
+ # Types the supplied text into the current window.
73
+ #
74
+ # @param [String] text The text
75
+ # @param [Hash] options The options
76
+ # * :delay (Numeric) _Optional_ The amount of time, in seconds, to delay
77
+ # after typing the text (defaults to 0.5)
78
+ def type(text, options = {})
79
+ @windowing_system.focus_window @pid
80
+ @windowing_system.type_combination text
81
+
82
+ sleep options[:delay] || 0.5
83
+ end
84
+
85
+ # Enters the supplied url into the current window.
86
+ #
87
+ # @param [String] url The url
88
+ # @param [Hash] options The options
89
+ # * :delay (Numeric) _Optional_ The amount of time, in seconds, to delay
90
+ # after entering the url (defaults to 0.5)
91
+ def visit(url, options = {})
92
+ run url, options
93
+ end
94
+ end
95
+ end
data/lib/mani/x.rb ADDED
@@ -0,0 +1,48 @@
1
+ require 'x_do'
2
+
3
+ class Mani
4
+ # This class contains methods to interface with the X Window System.
5
+ class X
6
+ # Initializes XDo to handle the heavy lifting of interfacing with X.
7
+ def initialize
8
+ @xdo = XDo.new
9
+ end
10
+
11
+ # Finds the visible window with the supplied pid and focuses it.
12
+ #
13
+ # @param [Fixnum] pid The pid
14
+ def focus_window(pid)
15
+ @xdo.find_windows(pid: pid, visible: true).first.focus
16
+ end
17
+
18
+ # Types the supplied combination. Note that any text between '{{' and '}}'
19
+ # delimiters is treated as a sequence. All other text is treated literally.
20
+ #
21
+ # @param [String] combination The combination
22
+ def type_combination(combination)
23
+ tokens = Mani::Tokenizer.get_tokens combination
24
+ tokens.each do |token|
25
+ case token.first
26
+ when :static
27
+ type_string token.last
28
+ when :sequence
29
+ type_keysequence token.last
30
+ end
31
+ end
32
+ end
33
+
34
+ # Types the supplied sequence (e.g., 'ctrl+v', 'F6', 'alt+2').
35
+ #
36
+ # @param [String] sequence The sequence
37
+ def type_keysequence(sequence)
38
+ @xdo.keyboard.type_keysequence sequence
39
+ end
40
+
41
+ # Types the supplied string. Note that the string is treated literally.
42
+ #
43
+ # @param [String] string The string
44
+ def type_string(string)
45
+ @xdo.keyboard.type_string string
46
+ end
47
+ end
48
+ end
data/lib/mani.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'mani/window'
2
+ require 'mani/tokenizer'
3
+
4
+ # The base class.
5
+ class Mani
6
+ # Initializes the windowing system, as well as the window manager options.
7
+ #
8
+ # @param [Hash] options The options
9
+ # * :window_manager (Symbol) The window manager (currently only handles
10
+ # :xmonad)
11
+ # * :switch_to_workspace (Proc) _Optional_ The proc which, when called,
12
+ # returns a string which, when interpreted, will switch to the specified
13
+ # workspace
14
+ # @param [Proc] block The code to execute after initialization
15
+ def initialize(options = {}, &block)
16
+ window_manager = options.delete :window_manager
17
+ case window_manager
18
+ when :xmonad
19
+ require 'mani/x'
20
+
21
+ @windowing_system = Mani::X.new
22
+ @window_manager_options = {
23
+ switch_to_workspace: ->(space) { "super+#{space}" }
24
+ }.merge options
25
+ else
26
+ fail 'Unrecognized :window_manager.'
27
+ end
28
+
29
+ @windows = {}
30
+
31
+ instance_eval(&block)
32
+ end
33
+
34
+ # Creates a new window object, or defers execution of the supplied block to
35
+ # an existing window object.
36
+ #
37
+ # @param [Symbol] name The window name
38
+ # @param [Hash] options The options
39
+ # * :launch (String) _Optional_ The program to launch. If specified, any
40
+ # remaining options, as well as the supplied block, will be passed to
41
+ # Mani::Window#launch. If not specified, the supplied block will be
42
+ # executed (within the context of the window).
43
+ # @param [Proc] block The code to execute
44
+ def window(name, options = {}, &block)
45
+ @windows[name] = Window.new @windowing_system unless @windows[name]
46
+
47
+ program = options.delete :launch
48
+ if program
49
+ @windows[name].launch program, options, &block
50
+ else
51
+ @windows[name].instance_eval(&block)
52
+ end
53
+ end
54
+
55
+ # Switches to the specified workspace.
56
+ #
57
+ # @param [Integer] space The space number
58
+ # @param [Proc] block The code to execute after switching to the workspace
59
+ def workspace(space, &block)
60
+ sequence = @window_manager_options[:switch_to_workspace].call space
61
+ @windowing_system.type_keysequence sequence
62
+
63
+ instance_eval(&block) if block
64
+ end
65
+ end
data/mani.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'mani/version'
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = 'mani'
6
+ gem.homepage = 'https://github.com/NSinopoli/mani'
7
+ gem.license = 'BSD (3-Clause)'
8
+ gem.summary = 'A window automation tool'
9
+ gem.description = 'A window automation tool'
10
+ gem.email = 'NSinopoli@gmail.com'
11
+ gem.authors = ['Nick Sinopoli']
12
+
13
+ gem.version = Mani::Version::VERSION
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{^(spec)/})
18
+ gem.require_paths = ['lib']
19
+ end
@@ -0,0 +1,237 @@
1
+ require 'minitest/autorun'
2
+ require_relative '../../lib/mani/tokenizer'
3
+
4
+ describe Mani::Tokenizer do
5
+ describe '.get_tokens' do
6
+ describe 'when there are no tags' do
7
+ it 'returns the correct tokens' do
8
+ source = 'static'
9
+ actual = Mani::Tokenizer.get_tokens source
10
+ expected = [[:static, 'static']]
11
+ actual.must_equal expected
12
+ end
13
+ end
14
+
15
+ describe 'when there is an opening tag and no closing tag' do
16
+ it 'returns the correct tokens' do
17
+ source = 'static {{ static'
18
+ actual = Mani::Tokenizer.get_tokens source
19
+ expected = [
20
+ [:static, 'static {{ static'],
21
+ ]
22
+ actual.must_equal expected
23
+ end
24
+ end
25
+
26
+ describe 'when there is a closing tag and no opening tag' do
27
+ it 'returns the correct tokens' do
28
+ source = 'static }} static'
29
+ actual = Mani::Tokenizer.get_tokens source
30
+ expected = [
31
+ [:static, 'static }} static']
32
+ ]
33
+ actual.must_equal expected
34
+ end
35
+ end
36
+
37
+ describe 'when there is a commented opening tag and no closing tag' do
38
+ it 'returns the correct tokens' do
39
+ source = 'static %{{ static'
40
+ actual = Mani::Tokenizer.get_tokens source
41
+ expected = [
42
+ [:static, 'static {{ static']
43
+ ]
44
+ actual.must_equal expected
45
+ end
46
+ end
47
+
48
+ describe 'when there is a commented closing tag and no opening tag' do
49
+ it 'returns the correct tokens' do
50
+ source = 'static %}} static'
51
+ actual = Mani::Tokenizer.get_tokens source
52
+ expected = [
53
+ [:static, 'static }} static']
54
+ ]
55
+ actual.must_equal expected
56
+ end
57
+ end
58
+
59
+ describe 'when there are commented opening and closing tags' do
60
+ it 'returns the correct tokens' do
61
+ source = 'static %{{seq%}} static'
62
+ actual = Mani::Tokenizer.get_tokens source
63
+ expected = [
64
+ [:static, 'static {{seq}} static'],
65
+ ]
66
+ actual.must_equal expected
67
+ end
68
+ end
69
+
70
+ describe 'when there is a commented opening tag and a closing tag' do
71
+ it 'returns the correct tokens' do
72
+ source = 'static %{{seq}} static'
73
+ actual = Mani::Tokenizer.get_tokens source
74
+ expected = [
75
+ [:static, 'static {{seq}} static'],
76
+ ]
77
+ actual.must_equal expected
78
+ end
79
+ end
80
+
81
+ describe 'when there is an opening tag and a commented closing tag' do
82
+ it 'returns the correct tokens' do
83
+ source = 'static {{seq%}} static'
84
+ actual = Mani::Tokenizer.get_tokens source
85
+ expected = [
86
+ [:static, 'static {{seq}} static'],
87
+ ]
88
+ actual.must_equal expected
89
+ end
90
+ end
91
+
92
+ describe 'when there is an empty sequence' do
93
+ it 'returns the correct tokens' do
94
+ source = '{{}}'
95
+ actual = Mani::Tokenizer.get_tokens source
96
+ expected = []
97
+ actual.must_equal expected
98
+ end
99
+ end
100
+
101
+ describe 'when there is one, unnested sequence' do
102
+ it 'returns the correct tokens' do
103
+ source = 'static {{seq}} static'
104
+ actual = Mani::Tokenizer.get_tokens source
105
+ expected = [
106
+ [:static, 'static '],
107
+ [:sequence, 'seq'],
108
+ [:static, ' static']
109
+ ]
110
+ actual.must_equal expected
111
+ end
112
+ end
113
+
114
+ describe 'when there is a sequence with a commented opening tag within' do
115
+ it 'returns the correct tokens' do
116
+ source = 'static {{seq %{{ }} static'
117
+ actual = Mani::Tokenizer.get_tokens source
118
+ expected = [
119
+ [:static, 'static '],
120
+ [:sequence, 'seq {{ '],
121
+ [:static, ' static']
122
+ ]
123
+ actual.must_equal expected
124
+ end
125
+ end
126
+
127
+ describe 'when there is a sequence with a commented closing tag within' do
128
+ it 'returns the correct tokens' do
129
+ source = 'static {{seq %}} }} static'
130
+ actual = Mani::Tokenizer.get_tokens source
131
+ expected = [
132
+ [:static, 'static '],
133
+ [:sequence, 'seq }} '],
134
+ [:static, ' static']
135
+ ]
136
+ actual.must_equal expected
137
+ end
138
+ end
139
+
140
+ describe 'when there are commented tags within a sequence' do
141
+ it 'returns the correct tokens' do
142
+ source = 'static {{seq %{{ nested %}} }} static'
143
+ actual = Mani::Tokenizer.get_tokens source
144
+ expected = [
145
+ [:static, 'static '],
146
+ [:sequence, 'seq {{ nested }} '],
147
+ [:static, ' static']
148
+ ]
149
+ actual.must_equal expected
150
+ end
151
+ end
152
+
153
+ describe 'when there is a sequence within commented tags' do
154
+ it 'returns the correct tokens' do
155
+ source = 'static %{{comment {{seq}} %}} static'
156
+ actual = Mani::Tokenizer.get_tokens source
157
+ expected = [
158
+ [:static, 'static {{comment '],
159
+ [:sequence, 'seq'],
160
+ [:static, ' }} static']
161
+ ]
162
+ actual.must_equal expected
163
+ end
164
+ end
165
+
166
+ describe 'when there is a sequence within a sequence' do
167
+ it 'returns the correct tokens' do
168
+ source = 'static {{nested {{seq}} }} static'
169
+ actual = Mani::Tokenizer.get_tokens source
170
+ expected = [
171
+ [:static, 'static '],
172
+ [:sequence, 'nested {{seq'],
173
+ [:static, ' }} static']
174
+ ]
175
+ actual.must_equal expected
176
+ end
177
+ end
178
+
179
+ describe 'when there are multiple, unnested sequence' do
180
+ it 'returns the correct tokens' do
181
+ source = '{{seq}} static {{second seq}} more static'
182
+ actual = Mani::Tokenizer.get_tokens source
183
+ expected = [
184
+ [:sequence, 'seq'],
185
+ [:static, ' static '],
186
+ [:sequence, 'second seq'],
187
+ [:static, ' more static']
188
+ ]
189
+ actual.must_equal expected
190
+ end
191
+ end
192
+
193
+ describe 'when there are multiple empty sequence' do
194
+ it 'returns the correct tokens' do
195
+ source = '{{}} static {{}}'
196
+ actual = Mani::Tokenizer.get_tokens source
197
+ expected = [
198
+ [:static, ' static '],
199
+ ]
200
+ actual.must_equal expected
201
+ end
202
+ end
203
+
204
+ describe 'when the tags are out of order' do
205
+ it 'returns the correct tokens' do
206
+ source = '}} static {{'
207
+ actual = Mani::Tokenizer.get_tokens source
208
+ expected = [
209
+ [:static, '}} static {{'],
210
+ ]
211
+ actual.must_equal expected
212
+ end
213
+ end
214
+
215
+ describe 'when the commenting tags are out of order' do
216
+ it 'returns the correct tokens' do
217
+ source = '%}} static %{{'
218
+ actual = Mani::Tokenizer.get_tokens source
219
+ expected = [
220
+ [:static, '}} static {{'],
221
+ ]
222
+ actual.must_equal expected
223
+ end
224
+ end
225
+
226
+ describe 'when literal commenting tags are used' do
227
+ it 'returns the correct tokens' do
228
+ source = '%%{{ static %%}} more static'
229
+ actual = Mani::Tokenizer.get_tokens source
230
+ expected = [
231
+ [:static, '%{{ static %}} more static'],
232
+ ]
233
+ actual.must_equal expected
234
+ end
235
+ end
236
+ end
237
+ end
data/spec.rb ADDED
@@ -0,0 +1,3 @@
1
+ Dir.glob('spec/**/*.rb').each do |file|
2
+ require_relative file.sub(/\.rb$/, '')
3
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mani
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Nick Sinopoli
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A window automation tool
14
+ email: NSinopoli@gmail.com
15
+ executables:
16
+ - mani
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - .gitignore
21
+ - .rubocop.yml
22
+ - Gemfile
23
+ - Gemfile.lock
24
+ - LICENSE
25
+ - Makefile
26
+ - README.md
27
+ - bin/mani
28
+ - examples/chording.rb
29
+ - examples/hello.rb
30
+ - examples/workspace_initializer.rb
31
+ - lib/mani.rb
32
+ - lib/mani/tokenizer.rb
33
+ - lib/mani/version.rb
34
+ - lib/mani/window.rb
35
+ - lib/mani/x.rb
36
+ - mani.gemspec
37
+ - spec.rb
38
+ - spec/lib/tokenizer_spec.rb
39
+ homepage: https://github.com/NSinopoli/mani
40
+ licenses:
41
+ - BSD (3-Clause)
42
+ metadata: {}
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - '>='
50
+ - !ruby/object:Gem::Version
51
+ version: '0'
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ requirements: []
58
+ rubyforge_project:
59
+ rubygems_version: 2.1.4
60
+ signing_key:
61
+ specification_version: 4
62
+ summary: A window automation tool
63
+ test_files:
64
+ - spec/lib/tokenizer_spec.rb