myterm 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/._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: []