myterm 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/._repath.rb ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ payload = lambda do
4
+
5
+ bin_folder="#{`pwd`.strip}/bin"
6
+ unless File.directory?(bin_folder)
7
+ return {
8
+ :message => "not a directory, won't add to PATH: #{bin_folder}"
9
+ }
10
+ end
11
+
12
+ path = ENV['PATH']
13
+ path_parts = path.split(':')
14
+
15
+ if path.include?(bin_folder)
16
+ if true or ARGV.include?('-F')
17
+ unless path_parts.reject! { |p| p == bin_folder }
18
+ return {
19
+ :message => "bin folder \"#{bin_folder}\" not found in path \"#{path}\""
20
+ }
21
+ end
22
+ return {
23
+ :new_path => ( [bin_folder] + [path_parts] ).join(':'),
24
+ :message => "rewriting path to have bin folder at the beginning",
25
+ :success => true
26
+ }
27
+ else
28
+ if 0 == path.index(bin_folder)
29
+ return {
30
+ :message => "bin folder is already at front of PATH",
31
+ :success => true
32
+ }
33
+ else
34
+ return {
35
+ :message => "bin folder is in path but not at front. use -F to rewrite PATH",
36
+ :status => :not_front,
37
+ :success => true
38
+ }
39
+ end
40
+ end
41
+ else
42
+ path_parts.unshift(bin_folder)
43
+ return {
44
+ :new_path => path_parts.join(':'),
45
+ :message => "prepending bin folder to the beginning of the PATH.",
46
+ :success => true
47
+ }
48
+ end
49
+
50
+ end.call
51
+
52
+ payload[:message] and $stdout.puts "echo #{payload[:message].inspect}"
53
+ payload[:new_path] and $stdout.puts "export PATH=\"#{payload[:new_path]}\""
54
+ $stdout.puts(payload[:success] ? "echo 'hack done.'" : "echo 'hack failed.'")
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.DS_Store
2
+ /.__tmp.sh
3
+ /.rvmrc
data/.repath.sh ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ruby ./._repath.rb 1> .__tmp.sh
4
+ source ./.__tmp.sh
data/.rvmrc.example ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.2@skylab
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ myterm (0.0.1)
5
+ highline
6
+ rb-appscript
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ archive-tar-minitar (0.5.2)
12
+ columnize (0.3.4)
13
+ highline (1.6.2)
14
+ linecache19 (0.5.12)
15
+ ruby_core_source (>= 0.1.4)
16
+ rb-appscript (0.6.1)
17
+ ruby-debug-base19 (0.11.25)
18
+ columnize (>= 0.3.1)
19
+ linecache19 (>= 0.5.11)
20
+ ruby_core_source (>= 0.1.4)
21
+ ruby-debug19 (0.11.6)
22
+ columnize (>= 0.3.1)
23
+ linecache19 (>= 0.5.11)
24
+ ruby-debug-base19 (>= 0.11.19)
25
+ ruby_core_source (0.1.5)
26
+ archive-tar-minitar (>= 0.5.2)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ myterm!
33
+ ruby-debug19
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # myterm
2
+
3
+ ### _(command line api for playing with iTerm)_
4
+
5
+ ## About
6
+
7
+ `myterm` is a command line interface for customizing iTerm using AppleScript. Its most useful feature is that it can throw up a big message in the background of your terminal tab to tell you clearly which tab it is.
8
+
9
+
10
+
11
+
12
+ ## Usage
13
+
14
+ `myterm` attempts to be as self-documenting as possible. Try `myterm -h` for help.
15
+
16
+ The most common use case for me personally is something like:
17
+
18
+ myterm bg --exec node server.js
19
+
20
+ which will display the string "node server.js" up in the background and then run that.
21
+
22
+
23
+
24
+
25
+ ## Requirements
26
+
27
+ `myterm` needs ImageMagick and its `convert` executable to be in your path.
28
+
29
+ `myterm` needs some font file to use. It has an installer that will try to grab the [Simple Life](http://www.dafont.com/simple-life.font) font by Michael Strobel.
30
+
31
+ Additionally it needs whatever its gem dependencie(s) are, which at the time of this writing are: rb-appscript, highline.
32
+
33
+
34
+
35
+
36
+ ## Installation
37
+
38
+ `myterm` is a rubygem. If you are installing it from a git checkout
39
+ (which at the time of this writing is the only way to install it):
40
+
41
+ mkdir ~/src; cd ~/src
42
+ git clone git@github.com:hipe/myterm.git; cd myterm
43
+ gem build *.gemspec
44
+ gem install *.gem
45
+
46
+
47
+ ## Support
48
+
49
+ I want this to work for you. Please contact me via email if it does not!
50
+
51
+
52
+
53
+
54
+ ## Credits
55
+
56
+ `myterm` started life as an adaptation of Dmytro Shteflyuk's script that he used at Scribd as described [here](http://kpumuk.info/mac-os-x/how-to-show-ssh-host-name-on-the-iterms-background/). Thank you Dmytro!
data/bin/myterm ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path('../../lib/myterm/cli', __FILE__)
4
+
5
+ Skylab::Myterm::Cli.new.run ARGV
data/doc/HISTORY.md ADDED
@@ -0,0 +1,17 @@
1
+ # myterm Change History
2
+
3
+ ## Release 0.0.3 - 2011-08-06
4
+
5
+ * fix issue with transparency - didn't make refactor
6
+
7
+
8
+ ## Relelase 0.0.2 - 2011-08-04
9
+
10
+ * Add installer for default font file when none is found
11
+ * Optionally use iTerm foreground color for background image foreground color
12
+ * Total refactor of code: separated into a 'CLI' and an 'API'
13
+
14
+
15
+ ## Release 0.0.1
16
+
17
+ * initial release, creates background images
data/lib/myterm/api.rb ADDED
@@ -0,0 +1,189 @@
1
+ module Skylab; end
2
+ module Skylab::Myterm; end
3
+
4
+ class Skylab::Myterm::ValidationError < RuntimeError ; end
5
+
6
+ module Skylab::Myterm::Color
7
+ Myterm = Skylab::Myterm
8
+ def self.[] obj
9
+ obj.extend self
10
+ end
11
+ def self.dup color
12
+ self[color.dup]
13
+ end
14
+ def alpha= mixed
15
+ if mixed.kind_of?(String)
16
+ md = /\A(\d+(?:\.\d+)?)%?\z/.match(mixed) or
17
+ raise Myterm::ValidationError.new("invalid format for percent #{val.inspect} -- expecting e.g. \"58%\"")
18
+ mixed = md[1].to_f
19
+ end
20
+ (0.0..100.0).include?(mixed) or
21
+ raise Myterm::ValidationError.new("Percent value (#{mixed}%) must be between 0 and 100 inclusive.")
22
+ self[3] = Myterm::ChannelScalarNormalized[mixed / 100.0]
23
+ end
24
+ def to_hex
25
+ '#' + self.map{ |x| int_to_hex(x) }.join('')
26
+ end
27
+ TargetPlaces = 2 # each component of an #rrggbb hexadecimal color has 2 places
28
+ Divisor = 16 ** TargetPlaces
29
+ def int_to_hex int
30
+ int.respond_to?(:to_hex) and return int.to_hex
31
+ (int.to_f / Divisor).round.to_s(16).rjust(TargetPlaces, '0')
32
+ end
33
+ end
34
+
35
+ module Skylab::Myterm::ChannelScalarNormalized
36
+ def self.[] obj
37
+ obj.extend self
38
+ end
39
+ def to_hex
40
+ (('ff'.to_i(16).to_f * self).to_i).to_s(16).rjust(2, '0')
41
+ end
42
+ end
43
+
44
+ class Skylab::Myterm::ImageBuilder
45
+ Myterm = Skylab::Myterm
46
+ class << self
47
+ def build_background_image iterm, lines, opts
48
+ new(iterm, lines, opts).run
49
+ end
50
+ end
51
+ def initialize iterm, lines, opts
52
+ @iterm, @lines, @opts = [iterm, lines, opts]
53
+ end
54
+ attr_reader :iterm
55
+ def run
56
+ require 'RMagick'
57
+ bg_color = Myterm::Color.dup(@iterm.session.background_color)
58
+ @opts.key?(:alpha_percent) and bg_color.alpha = @opts[:alpha_percent]
59
+ img = Magick::Image.new(500, 300) do # copying over hard-coded dimensions from original Dmytro script
60
+ self.background_color = bg_color.to_hex
61
+ end
62
+ build_text_drawing img
63
+ img
64
+ end
65
+ private
66
+ def build_text_drawing img
67
+ @lines.empty? and return fail("foo")
68
+ #@todo setters for everything etc
69
+ draw = Magick::Draw.new
70
+ draw.gravity = @opts[:gravity] || Magick::NorthEastGravity
71
+ @opts[:fill] ||= '#662020'
72
+ @opts[:fill].kind_of?(Proc) and @opts[:fill] = @opts[:fill].call(self)
73
+ draw.fill = @opts[:fill]
74
+ draw.font = @opts[:font] || "#{ENV['HOME']}/.fonts/SimpleLife.ttf"
75
+ draw.font_style = @opts[:font_style] || Magick::NormalStyle
76
+ draw.pointsize = @opts[:point_size] || 60
77
+ draw.text_antialias = @opts.key?(:text_antialias) ? @opts[:text_antialias] : true
78
+ draw.annotate(img, 0,0,20,10, @lines.first)
79
+ if @lines.length > 1
80
+ second_line = @lines[1..-1].join(' ')
81
+ draw.annotate(img, 0,0,20,80, second_line) do
82
+ self.pointsize = 30
83
+ end
84
+ end
85
+ nil # draw not needed at this point
86
+ end
87
+ end
88
+
89
+ class Skylab::Myterm::ItermProxy
90
+ # ItermProxy is a wrapper around everything Iterm to the extent that its AppleScript interface supports
91
+
92
+ Myterm = ::Skylab::Myterm # keep top level name out of the bulk of the code
93
+
94
+ MinLen = 50
95
+
96
+ def bounds= arr
97
+ x = arr.detect { |i| i.to_s !~ /^\d+$/ } and return invalid("expecting digit had #{x.inspect}")
98
+ x = arr[2,2].detect { |i| i.to_i < MinLen } and return invalid("too small: #{x} (min: #{MinLen})")
99
+ app.windows[0].bounds.set arr
100
+ end
101
+
102
+ def bounds
103
+ app.windows[0].bounds.get
104
+ end
105
+
106
+ def session
107
+ tty = `tty`.strip
108
+ @session ||= begin
109
+ session = catch(:catch_two) do
110
+ app.terminals.get.each do |term|
111
+ sessions = term.sessions.get
112
+ sessions.each do |session|
113
+ if session.tty.get == tty
114
+ throw :catch_two, session
115
+ end
116
+ end
117
+ end
118
+ end
119
+ session or fail("couldn't ascertain current session!")
120
+ SessionProxy.new(session)
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ def app
127
+ @app ||= begin
128
+ require 'appscript'
129
+ Appscript.app('iTerm')
130
+ end
131
+ end
132
+
133
+ def invalid msg
134
+ raise Myterm::ValidationError.new(msg)
135
+ end
136
+ end
137
+
138
+ module Skylab::Myterm::AppscriptDelegator
139
+ def delegated_attr_readers *list
140
+ list.each do |property|
141
+ lambda do |_property|
142
+ define_method(_property) do
143
+ @resource.send(_property).get
144
+ end
145
+ end.call(property)
146
+ end
147
+ end
148
+ def delegated_attr_writers *list
149
+ list.each do |property|
150
+ lambda do |_property|
151
+ define_method("#{_property}=") do |val|
152
+ @resource.send(_property).set val
153
+ end
154
+ end.call(property)
155
+ end
156
+ end
157
+ def delegated_attr_accessors *list
158
+ delegated_attr_readers(*list)
159
+ delegated_attr_writers(*list)
160
+ end
161
+ end
162
+
163
+ class Skylab::Myterm::ItermProxy::SessionProxy
164
+ Myterm = Skylab::Myterm
165
+ def initialize session
166
+ @resource = session
167
+ end
168
+ extend Myterm::AppscriptDelegator
169
+
170
+ delegated_attr_accessors :background_image_path
171
+ delegated_attr_readers :tty
172
+
173
+ def background_color
174
+ Myterm::Color[@resource.background_color.get]
175
+ end
176
+
177
+ def foreground_color
178
+ Myterm::Color[@resource.foreground_color.get]
179
+ end
180
+ end
181
+
182
+ module Skylab::Myterm::ChannelScalarNormalized
183
+ def self.[] obj
184
+ obj.extend self
185
+ end
186
+ def to_hex
187
+ (('ff'.to_i(16).to_f * self).to_i).to_s(16).rjust(2, '0')
188
+ end
189
+ end
data/lib/myterm/cli.rb ADDED
@@ -0,0 +1,149 @@
1
+ require "#{File.expand_path('../vendor/face/cli', __FILE__)}"
2
+ require "#{File.dirname(__FILE__)}/api"
3
+ require 'ruby-debug'
4
+ require 'open3'
5
+
6
+ module Skylab; end
7
+ module Skylab::Myterm; end
8
+
9
+ module Skylab::Myterm::PathPrettifier
10
+ HomeDirRe = /\A#{Regexp.escape(ENV['HOME'])}/
11
+ protected
12
+ def pretty_path path
13
+ path.sub(HomeDirRe, '~')
14
+ end
15
+ end
16
+
17
+ class Tmx::Face::Command
18
+ include Skylab::Myterm::PathPrettifier
19
+ end
20
+
21
+ class Skylab::Myterm::Cli < Tmx::Face::Cli
22
+ Myterm = ::Skylab::Myterm # don't use fully qualified name internally
23
+ include Myterm::PathPrettifier
24
+
25
+ version do
26
+ require "#{File.dirname(__FILE__)}/version"
27
+ Myterm::VERSION
28
+ end
29
+
30
+ o(:bounds) do |o|
31
+ syntax "#{path} [x y width height]"
32
+ o.banner = "gets/sets the bounds of the terminal window\n#{usage_string}"
33
+ end
34
+
35
+ def bounds o, *a
36
+ case a.length
37
+ when 4
38
+ begin ; iterm.bounds = a ; rescue Myterm::ValidationError => e ; return usage e ; end
39
+ when 0
40
+ @err.puts iterm.bounds.inspect
41
+ else
42
+ return usage("bad number of args #{a.length}: expecting 0 or 4")
43
+ end
44
+ end
45
+
46
+ o(:'bg') do |o, req|
47
+ syntax "#{path} [opts] [<text> [<text> [...]]]"
48
+ o.banner = "Generate a background image with certain text for the terminal\n#{usage_string}"
49
+ o.on('-e', '--exec <cmd ...>', 'Execute <cmd ...> in shell, also use it as text for background.') { }
50
+ o.on('-o', '--opacity PERCENT', "Percent by which to make image background opaque",
51
+ "(0%: tranparent. 100%: solid. Default: solid)") { |amt| req[:alpha_percent] = amt }
52
+ req[:font_file] = DefaultFontFile
53
+ o.on('--font FONTFILE.ttf', "font to use (default: #{pretty_path(req[:font_file])})") do |path|
54
+ req[:font_file] = path
55
+ end
56
+ req[:fill] = '#662020'
57
+ o.on('--fill[=COLOR]', "Write text in this color (default: #{req[:fill].inspect})",
58
+ "(when present but with no value, will use \"Text/Normal\" setting of current iTerm tab)" ) do |v|
59
+ req[:fill] = v || lambda { |img| img.iterm.session.foreground_color.to_hex }
60
+ end
61
+ o.on('-v', '--verbose', 'Be verbose.') { req[:verbose] = true }
62
+ end
63
+
64
+ def before_parse_bg req, args
65
+ idx = args.index { |s| %w(-e --exec).include?(s) } or return true
66
+ req[:_exec_this] = args[(idx+1)..-1]
67
+ args.replace idx == 0 ? [] : args[0..(idx-1)]
68
+ true
69
+ end
70
+ protected :before_parse_bg
71
+
72
+ def bg req, *args
73
+ if args.empty?
74
+ if req[:_exec_this]
75
+ args.any? and fail("logic error -- see before_parse_bg.")
76
+ args = req[:_exec_this]
77
+ else
78
+ return get_background
79
+ end
80
+ end
81
+ check_font(req) or return
82
+ img = Myterm::ImageBuilder.build_background_image(iterm, args, req) or return false
83
+ req[:verbose] and @err.puts "(bg_color: #{img.background_color.inspect})"
84
+ outpath = "#{ImgDirname}/#{ImgBasename}.#{Process.pid}.png"
85
+ img.write(outpath)
86
+ req[:verbose] and @err.puts "(setting background image to: #{outpath})" # doesn't care if --verbose
87
+ iterm.session.background_image_path = outpath
88
+ if req[:_exec_this]
89
+ @err.puts "(#{program_name} executing: #{req[:_exec_this].join(' ')})"
90
+ exec(req[:_exec_this].join(' '))
91
+ end
92
+ true
93
+ end
94
+
95
+ DefaultFontFile = "#{ENV['HOME']}/.fonts/MytermDefaultFont.ttf"
96
+ ImgDirname = '/tmp'
97
+ ImgBasename = 'iTermBG'
98
+
99
+ private
100
+ def check_font req
101
+ File.exist?(req[:font_file]) and return true
102
+ if req[:font_file] == DefaultFontFile
103
+ return maybe_download_font req
104
+ else
105
+ font_not_found req
106
+ end
107
+ end
108
+
109
+ DefaultFontUrl = 'http://img.dafont.com/dl/?f=simple_life'
110
+ DefaultFontFileNotSimlinked = "#{ENV['HOME']}/.fonts/SimpleLife.ttf"
111
+
112
+ def maybe_download_font req
113
+ target = DefaultFontFileNotSimlinked
114
+ File.exist?(target) and return true
115
+ $stdin.tty? && $stdout.tty? or return font_not_found(req)
116
+ @err.write "Font file #{pretty_path(req[:font_file])} not found. "
117
+ require 'highline'
118
+ require 'fileutils'
119
+ HighLine.new.agree("Let #{program_name} download it? (Y/n) (recommended: yes)") or return false
120
+ outfile = DefaultFontFileNotSimlinked.sub(/\.ttf$/, '.zip')
121
+ font_dir = File.dirname(outfile)
122
+ File.directory?(font_dir) or FileUtils.mkdir_p(font_dir, :verbose => true)
123
+ cmds = ["wget -O #{outfile} #{DefaultFontUrl}"]
124
+ cmds.push "cd #{font_dir}"
125
+ cmds.push "unzip #{outfile}"
126
+ cmds.push "ln -s #{DefaultFontFileNotSimlinked} #{DefaultFontFile}"
127
+ cmds.push("echo 'finished installing for #{program_name}: #{pretty_path(DefaultFontFileNotSimlinked)}. " <<
128
+ "Please try using it again.'")
129
+ @err.puts(cmd = cmds.join(' ; '))
130
+ exec(cmd)
131
+ end
132
+
133
+ def font_not_found req
134
+ @err.puts "font file not found: #{pretty_path(req[:font_file])}"
135
+ req.command.usage
136
+ return false
137
+ end
138
+
139
+ def iterm
140
+ @iterm ||= Myterm::ItermProxy.new
141
+ end
142
+
143
+ def get_background
144
+ @err.puts "tty: #{iterm.session.tty}"
145
+ @err.puts "background_image: #{iterm.session.background_image_path.inspect}"
146
+ @err.puts "background_color: #{iterm.session.background_color}"
147
+ end
148
+
149
+ end
@@ -0,0 +1,391 @@
1
+ require 'optparse'
2
+
3
+ # an ultralight command-line parser (391 lines)
4
+ # that wraps around OptParse (can do anything it does)
5
+ # with colors
6
+ # with flexible command-like options ('officious' like -v, -h)
7
+ # with commands with arguments based off of method signatures
8
+ # with subcommands, (namespaces) arbitrarily deeply nested
9
+
10
+ module Tmx; end
11
+ module Tmx::Face; end
12
+
13
+ module Tmx::Face::Colors
14
+ def bold str ; style str, :bright, :green end
15
+ def hi str ; style str, :green end
16
+ def ohno str ; style str, :red end
17
+ def yelo str ; style str, :yellow end
18
+ Styles = { :bright => 1, :red => 31, :yellow => 33, :green => 32, :cyan => 36 }
19
+ Esc = "\e" # "\u001b" ok in 1.9.2
20
+ def style str, *styles
21
+ nums = styles.map{ |o| o.kind_of?(Integer) ? o : Styles[o] }.compact
22
+ "#{Esc}[#{nums.join(';')}m#{str}#{Esc}[0m"
23
+ end
24
+ def highlight_header str
25
+ str.sub(/\A([^:]+:)/) { "#{hi($1)}" }
26
+ end
27
+ end
28
+
29
+ module Tmx::Face
30
+ class Command
31
+
32
+ include Colors
33
+
34
+ def initialize name, *rest, &block
35
+ rest.any? and
36
+ raise ArgumentError.new("too many args for command: #{rest.inspect}")
37
+ @parser_definition = block # nil ok
38
+ @intern = name.to_sym
39
+ end
40
+
41
+ class << self
42
+ alias_method :build, :new
43
+ end
44
+
45
+ def build_option_parser req
46
+ parser = build_empty_option_parser
47
+ # we set the default value for the banner after we run the block
48
+ # to give the client the chance to set the syntax.
49
+ ugly, ugly_id = [parser.banner.dup, parser.banner.object_id]
50
+ if @parser_definition
51
+ args = [parser, req]
52
+ @parser_definition.arity > 0 and args = args[0, @parser_definition.arity]
53
+ instance_exec(*args, &@parser_definition)
54
+ end
55
+ if ugly == parser.banner && ugly_id = parser.banner.object_id
56
+ parser.banner = usage_string
57
+ end
58
+ parser
59
+ end
60
+
61
+ def for_run parent, name_as_used
62
+ self.parent = parent
63
+ @out = @parent.out
64
+ @err = @parent.err
65
+ @name_as_used = name_as_used
66
+ self # careful
67
+ end
68
+
69
+ def method
70
+ @parent.method method_symbol
71
+ end
72
+
73
+ def method_symbol
74
+ @intern.to_s.downcase.gsub(/[^a-z0-9]+/, '_').intern
75
+ end
76
+
77
+ def name
78
+ @intern.to_s
79
+ end
80
+
81
+ def parse argv
82
+ req = { }
83
+ req.send(:instance_variable_set, '@method_parameters', argv)
84
+ class << req
85
+ attr_accessor :method_parameters, :command
86
+ end
87
+ @parent_protected_instance_methods.include?("before_parse_#{@intern}".intern) and
88
+ ! @parent.send("before_parse_#{@intern}", req, argv) and return false
89
+ begin
90
+ build_option_parser(req).parse! argv
91
+ req
92
+ rescue OptionParser::ParseError => e
93
+ @out.puts highlight_header(e.to_s)
94
+ invite
95
+ nil
96
+ end
97
+ end
98
+
99
+ def summary
100
+ build_option_parser({}).to_s.
101
+ sub(/\A#{Regexp.escape(hi('usage:'))} /, '').
102
+ split("\n").select{ |s| ! s.strip.empty? }
103
+ end
104
+
105
+ def syntax *args
106
+ case args.length
107
+ when 0; @syntax ||= "#{invocation_string} [opts] [args]"
108
+ when 1; @syntax = args.first
109
+ else raise ArgumentError.new("expecting 0 or 1 argument")
110
+ end
111
+ end
112
+
113
+ def usage_string
114
+ "#{hi('usage:')} #{syntax}"
115
+ end
116
+
117
+ module Nodeish
118
+ def build_empty_option_parser
119
+ OptionParser.new
120
+ end
121
+ def invite
122
+ @err.puts "Try #{hi("#{invocation_string} -h")} for help."
123
+ nil
124
+ end
125
+ def invocation_string
126
+ "#{@parent.invocation_string} #{name}"
127
+ end
128
+ alias_method :path, :invocation_string
129
+ def parent= parent
130
+ @parent and fail("won't overwrite existing parent")
131
+ @parent_protected_instance_methods = parent.class.protected_instance_methods(false).map(&:intern)
132
+ @parent = parent
133
+ end
134
+ def usage msg=nil
135
+ msg and @err.puts(msg)
136
+ @err.puts usage_string
137
+ invite
138
+ end
139
+ alias_method :empty_argv, :usage
140
+ end
141
+ include Nodeish
142
+
143
+ module TreeDefiner
144
+ def command_tree
145
+ @command_tree ||= begin
146
+ defined = command_definitions.map { |cls, a, b| cls.build(*a, &b) }
147
+ defined_m = defined.map(&:method_symbol).compact
148
+ implied_m = public_instance_methods(false).map(&:intern) - defined_m
149
+ implied = implied_m.map { |m| Command.new(m) }
150
+ Treeish[ defined + implied ]
151
+ end
152
+ end
153
+ # this is nutty: for classes that extend this module, this is
154
+ # something that is triggered when they are subclasses
155
+ def inherited cls
156
+ cls.on('-h', '--help', 'show this screen') { help }
157
+ # You can rewrite the above in your class with another call to on()
158
+ # If you want to remove it, try:
159
+ # option_definitions.reject! { |a,_| '-h' == a.first }
160
+ end
161
+ def namespace name, &block
162
+ command_definitions.push [Namespace, [name], block]
163
+ end
164
+ def on *a, &b
165
+ block_given? or raise ArgumentError.new("block required")
166
+ option_definitions.push [a, b]
167
+ end
168
+ def option_definitions
169
+ @option_definitions ||= []
170
+ end
171
+ def command_definitions
172
+ @command_definitions ||= []
173
+ end
174
+ def method_added name
175
+ if @grab_next_method
176
+ command_definitions.last[1][0] = name.to_sym
177
+ @grab_next_method = false
178
+ end
179
+ end
180
+ def option_parser *a, &b
181
+ block_given? or raise ArgumentError.new("block required")
182
+ if a.empty?
183
+ @grab_next_method and fail("can't have two anonymous " <<
184
+ "command definitions in a row.")
185
+ @grab_next_method = true
186
+ a = [nil]
187
+ end
188
+ command_definitions.push [Command, a, b]
189
+ end
190
+ alias_method :o, :option_parser
191
+ end
192
+
193
+ module Treeish
194
+ def self.[] ary
195
+ ary.extend self
196
+ end
197
+ def ambiguous_command found, given
198
+ usage("Ambiguous command: #{given.inspect}. " <<
199
+ " Did you mean #{found.map{ |c| hi(c.name) }.join(' or ')}?")
200
+ end
201
+ def command_tree
202
+ @command_tree ||= begin
203
+ interface.command_tree.map { |c| c.parent = self; c } # careful
204
+ end
205
+ end
206
+ def expecting
207
+ interface.command_tree.map(&:name) * '|'
208
+ end
209
+ def find_command argv
210
+ argv.empty? and return empty_argv # should be just for n/s
211
+ given = argv.first
212
+ matcher = Regexp.new(/\A#{Regexp.escape(given)}/)
213
+ found = []
214
+ interface.command_tree.each do |cmd|
215
+ given == cmd.name and found = [cmd] and break
216
+ matcher.match(cmd.name) and found.push(cmd)
217
+ end
218
+ case found.size
219
+ when 0 ; unrecognized_command given
220
+ when 1 ; found.first.for_run(self, argv.shift)
221
+ else ; ambiguous_command found, given
222
+ end
223
+ end
224
+ Indent = ' '
225
+ def help
226
+ option_parser and @err.puts option_parser
227
+ cmds = command_tree
228
+ if cmds.any?
229
+ @err.puts hi('commands:')
230
+ rows = cmds.map { |c| { :name => c.name, :lines => c.summary } }
231
+ w = rows.map{ |d| d[:name].length }.inject(0){ |m, l| m > l ? m : l }
232
+ fmt = "%#{w}s "
233
+ rows.each do |row|
234
+ @out.puts "#{Indent}#{hi(fmt % row[:name])}#{row[:lines].first}"
235
+ row[:lines][1..-1].each do |line|
236
+ @out.puts "#{Indent}#{fmt % ''}#{line}"
237
+ end
238
+ end
239
+ @err.puts("Try #{hi("#{invocation_string} [cmd] -h")} for command help.")
240
+ end
241
+ end
242
+ def option_parser
243
+ @option_parser.nil? or return @option_parser
244
+ op = build_empty_option_parser
245
+ op.banner = usage_string
246
+ if interface.option_definitions.any?
247
+ shorts = interface.option_definitions.map do |args, block|
248
+ op.on(*args) { instance_eval(&block) }
249
+ args.first
250
+ end
251
+ op.banner << "\n #{invocation_string} {#{shorts * '|'}}"
252
+ op.banner << "\n" << hi('options:')
253
+ end
254
+ @option_parser = op
255
+ end
256
+ def run_opts argv
257
+ begin
258
+ option_parser.parse! argv
259
+ rescue OptionParser::ParseError => e
260
+ @err.puts highlight_header(e.to_s)
261
+ invite
262
+ end
263
+ if argv.any?
264
+ @err.puts "(#{hi('ignoring:')} #{argv.map(&:inspect).join(', ')})"
265
+ end
266
+ true
267
+ end
268
+ def unrecognized_command given
269
+ usage("Unrecognized command: #{given.inspect}. Expecting: #{hi expecting}")
270
+ end
271
+ def usage_string
272
+ "#{hi('usage:')} #{invocation_string} " <<
273
+ "{#{interface.command_tree.map(&:name)*'|'}} [opts] [args]"
274
+ end
275
+ end
276
+
277
+ class Namespace
278
+ extend TreeDefiner, Colors
279
+ include Treeish, Nodeish, Colors
280
+ alias_method :interface, :class
281
+ def init_for_run parent, name_as_used
282
+ @name_as_used = name_as_used
283
+ @parent = parent
284
+ @out = @parent.out
285
+ @err = @parent.err
286
+ end
287
+ attr_reader :out, :err
288
+ def name
289
+ interface.namespace_name
290
+ end
291
+ alias_method :inspect, :name
292
+ def self.build name, &block
293
+ name = name.to_s
294
+ Class.new(self).class_eval do
295
+ self.namespace_name = name
296
+ x = class << self; self end
297
+ x.send(:define_method, :inspect) { "#<#{name}:Namespace>" }
298
+ x.send(:alias_method, :to_s, :inspect)
299
+ class_eval(&block)
300
+ self
301
+ end
302
+ end
303
+
304
+ class << self
305
+ def for_run parent, name_as_used
306
+ namespace_runner = new
307
+ namespace_runner.init_for_run parent, name_as_used
308
+ namespace_runner
309
+ end
310
+ def method_symbol
311
+ nil # for compat with etc
312
+ end
313
+ def name
314
+ @namespace_name
315
+ end
316
+ def namespace_name= ns_name
317
+ @namespace_name = ns_name.to_s
318
+ end
319
+ attr_reader :namespace_name
320
+ def parent= parent
321
+ @parent and fail("won't overwrite parent")
322
+ @parent = parent
323
+ end
324
+ def summary
325
+ a = command_tree.map { |c| hi(c.name) }
326
+ ["child command#{'s' if a.length != 1}: {#{a * '|'}}"]
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+
333
+ class Tmx::Face::Cli
334
+ extend Tmx::Face::Command::TreeDefiner
335
+ include Tmx::Face::Colors
336
+ include Tmx::Face::Command::Nodeish
337
+ include Tmx::Face::Command::Treeish
338
+
339
+ def initialize
340
+ @out = $stdout
341
+ @err = $stderr
342
+ end
343
+ attr_reader :out, :err
344
+ alias_method :interface, :class
345
+ def argument_error e, cmd
346
+ e.backtrace[0,2].detect { |s| s.match(/\A[^:]+/)[0] == __FILE__ } or raise e
347
+ msg = e.message.sub(/\((\d+) for (\d+)\)\Z/) do
348
+ "(#{$1.to_i - 1} for #{$2.to_i - 1})"
349
+ end
350
+ cmd.usage msg
351
+ end
352
+ def program_name
353
+ @program_name ||= File.basename($PROGRAM_NAME)
354
+ end
355
+ alias_method :invocation_string, :program_name
356
+ def run argv
357
+ argv.empty? and return empty_argv
358
+ runner = self
359
+ begin
360
+ argv.first =~ /^-/ and return runner.run_opts(argv)
361
+ cmd = runner.find_command(argv)
362
+ end while (cmd and cmd.respond_to?(:find_command) and runner = cmd)
363
+ cmd and req = cmd.parse(argv) and
364
+ begin
365
+ req.command = cmd
366
+ runner.send(cmd.method_symbol, req, * req.method_parameters)
367
+ rescue ArgumentError => e
368
+ argument_error e, cmd
369
+ end
370
+ end
371
+ def version
372
+ @err.puts hi([program_name, interface.version].compact.join(' '))
373
+ end
374
+ class << self
375
+ def version *a, &block
376
+ if a.any? and block
377
+ raise ArgumentError.new("can't process args and block together.")
378
+ elsif a.any? or block
379
+ option_definitions.detect { |arr, _| '-v' == arr[0] } or
380
+ on('-v', '--version', 'shows version') { version }
381
+ if block
382
+ @version = block
383
+ else
384
+ @version = a.length == 1 ? a.first : a
385
+ end
386
+ else
387
+ @version.kind_of?(Proc) ? @version.call : @version
388
+ end
389
+ end
390
+ end
391
+ end
@@ -0,0 +1,4 @@
1
+ module Skylab; end
2
+ module Skylab::Myterm
3
+ VERSION = "0.0.3"
4
+ end
data/lib-iterm.sh ADDED
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ## NOTE for `iterm` users -- this file is not used: it is here for reference.
4
+ ## (it lives in a different git repository)
5
+
6
+ # adapted from http://kpumuk.info/mac-os-x/how-to-show-ssh-host-name-on-the-iterms-background/
7
+
8
+ HEIGHT_TALL=750
9
+ WIDTH_NARROW=600
10
+ WIDTH_SCROLLBAR=15
11
+ HEIGHT_TITLEBAR_TABS=44
12
+
13
+ # arr=($(iterm_bounds_get))
14
+ function iterm_bounds_get {
15
+ local size=( $(
16
+ osascript -e "
17
+ tell application \"iTerm\"
18
+ get the bounds of the first window
19
+ end tell" | tr ',' ' '
20
+ ) )
21
+ echo "${size[@]}"
22
+ }
23
+
24
+ function iterm_bounds_set {
25
+ if [[ ! "$1,$2,$3,$4" =~ "^[0-9]+,[0-9]+,[0-9]+,[0-9]+$" ]] ; then
26
+ echo "bad args to item_bounds_set: ($1, $2, $3, $4)"
27
+ return
28
+ fi
29
+ local exec_me="
30
+ tell application \"iTerm\"
31
+ set the bounds of the first window to {$1, $2, $3, $4}
32
+ end tell
33
+ "
34
+ osascript -e "$exec_me"
35
+ }
36
+
37
+ function iterm_dimensions_get {
38
+ local size=($(iterm_bounds_get))
39
+ local x1=${size[0]} y1=${size[1]} x2=${size[2]} y2=${size[3]}
40
+ local w=$(( $x2 - $x1 - $WIDTH_SCROLLBAR))
41
+ local h=$(( $y2 - $y1 - $HEIGHT_TITLEBAR_TABS))
42
+ echo "${w}x${h}"
43
+ }
44
+
45
+ function iterm_dimensions_set {
46
+ local w=$1 h=$2
47
+ local b=($(iterm_bounds_get))
48
+ local x=${b[0]} y=${b[1]}
49
+ iterm_bounds_set $x $y $w $h
50
+ }
51
+
52
+ # check to see if we have the correct terminal for doing this kind of thing
53
+ # this won't work when we sudo something because TERM_PROGRAM isn't picked up
54
+ function iterm_ok {
55
+ if [ "$(tty)" == 'not a tty' ] || [ "$TERM_PROGRAM" != "iTerm.app" ] ; then
56
+ echo ''
57
+ else
58
+ echo 'ok'
59
+ fi
60
+ }
61
+
62
+ function iterm_set {
63
+ local prop=$1
64
+ local val=$2
65
+ local tty=$(tty)
66
+ osascript -e "
67
+ tell application \"iTerm\"
68
+ repeat with theTerminal in terminals
69
+ tell theTerminal
70
+ try
71
+ tell session id \"$tty\"
72
+ set bounds of window 1 to \"$val\"
73
+ end tell
74
+ on error errmesg number errn
75
+ end try
76
+ end tell
77
+ end repeat
78
+ end tell
79
+ "
80
+ }
81
+
82
+ function iterm_bg_image_make {
83
+ local text1=$1
84
+ local text2=$2
85
+ local color_bg=${3:-#000000}
86
+ local color_fg=${4:-#662020}
87
+ local dimensions=$(iterm_dimensions_get)
88
+ local font_ttf=${5:-$HOME/.bash/resources/SimpleLife.ttf}
89
+ local font_points=${6:-60}
90
+ local font_style=${7:-Normal} # Font style (Any, Italic, Normal, Oblique)
91
+ local gravity=${8:-NorthEast}
92
+ # Text gravity (NorthWest, North, NorthEast,
93
+ # West, Center, East, SouthWest, South, SouthEast)
94
+ local offset1=${8:-20,10}
95
+ local offset2=${9:-20,80}
96
+ local outpath="/tmp/iTermBG.$$.png"
97
+ convert \
98
+ -size "$dimensions" xc:"$color_bg" -gravity "$gravity" -fill "$color_fg" \
99
+ -font "$font_ttf" -style "$font_style" -pointsize "$font_points" \
100
+ -antialias -draw "text $offset1 '$text1'" \
101
+ -pointsize 30 -draw "text $offset2 '$text2'" \
102
+ "$outpath"
103
+ echo $outpath
104
+ }
105
+
106
+ function iterm_bg_image_delete {
107
+ local path=${1:-/tmp/itermBG.$$.png}
108
+ rm $path
109
+ }
110
+
111
+ function iterm_bg_image_empty {
112
+ local opath="/tmp/iTermBG.empty.png"
113
+ if [ ! -f /tmp/iTermBG.empty.png ]; then
114
+ local dims=$(iterm_dimensions_get)
115
+ convert -size "$dims" xc:"#000000" "$opath"
116
+ fi
117
+ echo $opath
118
+ }
119
+
120
+ function iterm_bg_image_set {
121
+ local tty=$(tty)
122
+ osascript -e "
123
+ tell application \"iTerm\"
124
+ repeat with theTerminal in terminals
125
+ tell theTerminal
126
+ try
127
+ tell session id \"$tty\"
128
+ set background image path to \"$1\"
129
+ end tell
130
+ on error errmesg number errn
131
+ end try
132
+ end tell
133
+ end repeat
134
+ end tell
135
+ "
136
+ }
137
+
138
+ function iterm_bg_color_set {
139
+ local tty=$(tty)
140
+ osascript -e "
141
+ tell application \"iTerm\"
142
+ repeat with theTerminal in terminals
143
+ tell theTerminal
144
+ try
145
+ tell session id \"$tty\"
146
+ set background image path to \"\"
147
+ set background color to \"$1\"
148
+ end tell
149
+ on error errmesg number errn
150
+ end try
151
+ end tell
152
+ end repeat
153
+ end tell
154
+ "
155
+ }
156
+
157
+ function iterm_window_title_set {
158
+ osascript -e "
159
+ tell application \"iTerm\"
160
+ set the name of the first window to \"$1\"
161
+ end tell
162
+ "
163
+ }
data/myterm.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "myterm/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "myterm"
7
+ s.version = Skylab::Myterm::VERSION
8
+ s.authors = ["Mark Meves"]
9
+ s.email = ["mark.meves@gmail.com"]
10
+ s.homepage = "http://botnoise.org"
11
+ s.summary = %q{Command line interface for customizing iTerm using AppleScript}
12
+ s.description = %q{Command line interface for customizing iTerm using AppleScript.
13
+ Creates meaningful, salient, eye-catching background images that help to discern between iTerm windows.}.gsub(/\n */, ' ')
14
+
15
+ s.rubyforge_project = "myterm"
16
+
17
+ s.add_dependency 'rb-appscript'
18
+ s.add_dependency 'highline'
19
+ s.add_development_dependency "ruby-debug19"
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ # s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: myterm
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Mark Meves
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-06 00:00:00.000000000 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rb-appscript
17
+ requirement: &70161455195900 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *70161455195900
26
+ - !ruby/object:Gem::Dependency
27
+ name: highline
28
+ requirement: &70161455195480 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *70161455195480
37
+ - !ruby/object:Gem::Dependency
38
+ name: ruby-debug19
39
+ requirement: &70161455195060 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *70161455195060
48
+ description: Command line interface for customizing iTerm using AppleScript. Creates
49
+ meaningful, salient, eye-catching background images that help to discern between
50
+ iTerm windows.
51
+ email:
52
+ - mark.meves@gmail.com
53
+ executables:
54
+ - myterm
55
+ extensions: []
56
+ extra_rdoc_files: []
57
+ files:
58
+ - ._repath.rb
59
+ - .gitignore
60
+ - .repath.sh
61
+ - .rvmrc.example
62
+ - Gemfile
63
+ - Gemfile.lock
64
+ - README.md
65
+ - bin/myterm
66
+ - doc/HISTORY.md
67
+ - lib-iterm.sh
68
+ - lib/myterm/api.rb
69
+ - lib/myterm/cli.rb
70
+ - lib/myterm/vendor/face/cli.rb
71
+ - lib/myterm/version.rb
72
+ - myterm.gemspec
73
+ has_rdoc: true
74
+ homepage: http://botnoise.org
75
+ licenses: []
76
+ post_install_message:
77
+ rdoc_options: []
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ! '>='
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project: myterm
94
+ rubygems_version: 1.6.2
95
+ signing_key:
96
+ specification_version: 3
97
+ summary: Command line interface for customizing iTerm using AppleScript
98
+ test_files: []