appear 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,144 @@
1
+ module Appear
2
+ module Util
3
+ # Class for joining objects based on hash value or method value.
4
+ # @see Join.join
5
+ class Join
6
+ # Join objects or hashes together where thier field values match. This
7
+ # method is analogous to a JOIN in SQL, althought the behavior is not
8
+ # exactly the same.
9
+ #
10
+ # @example
11
+ # foos = many_foos
12
+ # bars = many_bars
13
+ # foo_bars = Join.join(:common_attribute, foos, bars)
14
+ #
15
+ # # can still access all the properties on either a foo or a bar
16
+ # foo_bars.first.common_attribute
17
+ #
18
+ # # can access attributes by symbol, too
19
+ # foo_bars.first[:something_else]
20
+ #
21
+ # foo_bars is an array of Join instances. Reads from a foo_bar will read
22
+ # first from the foo, and then from the bar - this is based on the order of
23
+ # "tables" passed to Join.join().
24
+ #
25
+ # @param field [Symbol] the method or hash field name to join on.
26
+ # @param tables [Array<Any>] arrays of any sort of object, so long as it is
27
+ # either a hash, or has a method named `field`.
28
+ # @return [Array<Join>]
29
+ def self.join(field, *tables)
30
+ by_field = Hash.new { |h, k| h[k] = self.new }
31
+
32
+ tables.each do |table|
33
+ table.each do |row|
34
+ field_value = access(row, field)
35
+ joined = by_field[field_value]
36
+ joined.push!(row)
37
+ end
38
+ end
39
+
40
+ by_field.values.select do |joined|
41
+ joined.joined_count >= tables.length
42
+ end
43
+ end
44
+
45
+ # True if we can access the given field on an object, either by calling
46
+ # that method on the object, or by accessing using []
47
+ #
48
+ # @param obj [Any]
49
+ # @param field [Symbol, String]
50
+ # @return [Boolean]
51
+ def self.can_access?(obj, field)
52
+ if obj.respond_to?(field)
53
+ return true
54
+ elsif obj.respond_to?(:[])
55
+ return true
56
+ end
57
+ return false
58
+ end
59
+
60
+ # Access the given field on an object.
61
+ # Raises an error if the field cannot be accessed.
62
+ #
63
+ # @param obj [Any]
64
+ # @param field [Symbol, String]
65
+ # @return [Any] the value at that field
66
+ def self.access(obj, field)
67
+ if obj.respond_to?(field)
68
+ obj.send(field)
69
+ elsif obj.respond_to?(:[])
70
+ obj[field]
71
+ else
72
+ raise "cannot access #{field.inspect} on #{obj.inspect}"
73
+ end
74
+ end
75
+
76
+ # A Join is a union of data objects. You can use a Join to group objects of
77
+ # different types, so that you may read from whichever has a given field.
78
+ #
79
+ # It is more useful to use self.join to perform a join operation on
80
+ # collections than to create Join objects directly.
81
+ def initialize(*objs)
82
+ @objs = objs
83
+ end
84
+
85
+ # add another data object to this join.
86
+ #
87
+ # @param obj [Any]
88
+ def push!(obj)
89
+ @objs << obj
90
+ end
91
+
92
+ # get the number of objects in this join
93
+ #
94
+ # @return [Fixnum]
95
+ def joined_count
96
+ @objs.length
97
+ end
98
+
99
+ # Return the first member in the join that matches the given block.
100
+ # @yield [Object] join member.
101
+ def unjoin(&block)
102
+ @objs.find(&block)
103
+ end
104
+
105
+ # read a field from the join. Returns the first non-nil value we can read.
106
+ # @see self.access for information about how fields are accessed.
107
+ #
108
+ # @param sym [String, Symbol] the field name
109
+ # @return [Any, nil]
110
+ def [](sym)
111
+ result = nil
112
+
113
+ @objs.each do |obj|
114
+ if self.class.can_access?(obj, sym)
115
+ result = self.class.access(obj, sym)
116
+ end
117
+ break unless result.nil?
118
+ end
119
+
120
+ result
121
+ end
122
+
123
+ # the {#method_missing} implementation on a Join allows you to access valid
124
+ # fields with regular accessors.
125
+ #
126
+ # @param method [String, Symbol]
127
+ # @param args [Array<Any>] should have none
128
+ # @param block [Proc] should have none
129
+ def method_missing(method, *args, &block)
130
+ raise NoMethodError.new("Cannot access #{method.inspect}") unless respond_to?(method)
131
+ raise ArgumentError.new("Passed args to accessor") if args.length > 0
132
+ raise ArgumentError.new("Passed block to accessor") if block
133
+ self[method]
134
+ end
135
+
136
+ # @param sym [String, Symbol] name of the method
137
+ # @param priv [Boolean] default false
138
+ # @return [Boolean] true if we can respond to the given method name
139
+ def respond_to?(sym, priv = false)
140
+ super(sym, priv) || (@objs.any? { |o| self.class.can_access?(o, sym) })
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,83 @@
1
+ require 'thread'
2
+
3
+ module Appear
4
+ module Util
5
+ # A Memoizer memoizes calls to a block, skipping repeated work when the
6
+ # arguments are the same. Memoization is thread-safe, so it's safe to
7
+ # memoize pure computations that occur on different threads.
8
+ #
9
+ # @example memoize a method
10
+ # class Example
11
+ # def initialize
12
+ # @memo = Memoizer.new
13
+ # end
14
+ #
15
+ # def foo(a, b)
16
+ # @memo.call(a, b) do
17
+ # expensive_computaion(a, b)
18
+ # end
19
+ # end
20
+ # end
21
+ #
22
+ # @example memoize part of a computation
23
+ # class Example
24
+ # def initialize
25
+ # @memo = Memoizer.new
26
+ # end
27
+ #
28
+ # def foo(a, b)
29
+ # state = get_state(a, b)
30
+ # d = memo.call(state) { expensive_pure_computation(state) }
31
+ # [a, d]
32
+ # end
33
+ # end
34
+ class Memoizer
35
+ def initialize
36
+ @cache = {}
37
+ @cache_mutex = Mutex.new
38
+ @disable = false
39
+ end
40
+
41
+ # Memoize the call to a block. Any arguments given to this method will be
42
+ # passed to the given block.
43
+ #
44
+ # @param args [Array<Any>] memoization key
45
+ # @return [Any] result of the block
46
+ def call(*args)
47
+ raise ArgumentError.new('no block given') unless block_given?
48
+
49
+ if @disable
50
+ return yield
51
+ end
52
+
53
+ @cache_mutex.synchronize do
54
+ return @cache[args] if @cache.key?(args)
55
+ end
56
+
57
+ result = yield
58
+ @cache_mutex.synchronize do
59
+ @cache[args] = result
60
+ end
61
+ result
62
+ end
63
+
64
+ # Evict the cache
65
+ #
66
+ # @return [self]
67
+ def clear!
68
+ @cache_mutex.synchronize do
69
+ @cache = {}
70
+ end
71
+ self
72
+ end
73
+
74
+ # Disable memoization permanently on this instance.
75
+ #
76
+ # @return [self]
77
+ def disable!
78
+ @disable = true
79
+ self
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,57 @@
1
+ require 'appear/constants'
2
+
3
+ module Appear
4
+ module Util
5
+
6
+ # An immutable value type, similar to Struct, but with annotated
7
+ # attr_readers, that can be easily created from a Hash with the necessary
8
+ # fields.
9
+ class ValueClass
10
+ # Thrown if a value is not supplied for an attribute
11
+ class MissingValueError < ::Appear::Error; end
12
+
13
+ # @param data [Hash]
14
+ def initialize(data)
15
+ self.class.properties.each do |val|
16
+ begin
17
+ instance_variable_set("@#{val}", data.fetch(val))
18
+ rescue KeyError
19
+ raise MissingValueError.new("#{self.class.name}: no value for attribute #{val.inspect}")
20
+ end
21
+ end
22
+ end
23
+
24
+ # Define an attribute reader that can be populated by the constructor.
25
+ #
26
+ # @param name [Symbol] define an attr_reader with this name
27
+ # @param opts [Hash] options
28
+ # @option opts [Symbol] :var instance variable we should read from
29
+ #
30
+ # @!macro [attach] value_class_property
31
+ # @!method $1
32
+ # The $1 property.
33
+ def self.property(name, opts = {})
34
+ var_name = opts.fetch(:var, name)
35
+
36
+ @props ||= []
37
+ @props << var_name
38
+
39
+ # we could do super, but we want to allow defining :acttive? or so
40
+ class_eval "def #{name}; @#{var_name}; end"
41
+
42
+ var_name
43
+ end
44
+
45
+ # @return [Array<Symbol>] names of all properties
46
+ def self.properties
47
+ @props ||= []
48
+ if self.superclass.respond_to?(:properties)
49
+ self.superclass.properties + @props
50
+ else
51
+ @props
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,6 @@
1
+ module Appear
2
+ # This module stores non-service classes that have no relation to Appear's
3
+ # business domain.
4
+ module Util
5
+ end
6
+ end
data/lib/appear.rb CHANGED
@@ -1,6 +1,23 @@
1
+ # Appear your terminal programs in your gui!
2
+ #
3
+ # Appear is a tool for revealing a given process in your terminal. Given a
4
+ # process ID, `appear` finds the terminal emulator view (be it a window, tab,
5
+ # or pane) containing that process and shows it to you. Appear understands
6
+ # terminal multiplexers like `tmux`, so if your target process is in a
7
+ # multiplexer session, `appear` will reveal a client connected to that session,
8
+ # or start one if needed.
9
+ #
10
+ # Most users of this library will find the {Appear.appear} method sufficient,
11
+ # although you may construct and control library internals using the
12
+ # {Appear::Instance} class, which is our "main" class.
13
+ #
14
+ # Other useful ideas include the {Appear::BaseService} class, which is a
15
+ # super-simple dependency-injection base class.
16
+ #
17
+ # @author Jake Teton-Landis <just.1.jake@gmail.com>
1
18
  module Appear
2
- # This method is an easy public interface to Appear for ruby consumers.
3
19
  # Appear the given PID in your user interfaces.
20
+ # This method is an easy public interface to Appear for ruby consumers.
4
21
  # @param pid [Number] pid to Appear.
5
22
  # @param config [Appear::Config, nil] a config for adjusting verbosity and logging.
6
23
  def self.appear(pid, config = nil)
@@ -8,7 +25,44 @@ module Appear
8
25
  instance = Appear::Instance.new(config)
9
26
  instance.call(pid)
10
27
  end
28
+
29
+ # Build a command string that will execute `appear` with the given config and
30
+ # arguments. If `appear` is in your PATH, we will use that binary. Otherwise,
31
+ # we will call the script in ./bin/ folder near this library, which has a
32
+ # #!/usr/bin/env ruby shbang.
33
+ #
34
+ # You may optionally need to prepend "PATH=#{ENV['PATH']} " to the command if
35
+ # `tmux` is not in your command execution environment's PATH.
36
+ #
37
+ # Intended for use with the terminal-notifier gem.
38
+ # @see https://github.com/julienXX/terminal-notifier/tree/master/Ruby
39
+ #
40
+ # @example Show a notification that will raise your program
41
+ # require 'appear'
42
+ # require 'terminal-notifier'
43
+ # TerminalNotifier.notify('Click to appear!', :execute => Appear.build_command(Process.pid))
44
+ #
45
+ # @param pid [Number] pid to Appear.
46
+ # @param config [Appear::Config, nil] a config for adjusting verbosity and logging.
47
+ # @return [String] a shell command that will execute `appear`
48
+ def self.build_command(pid, config = nil)
49
+ binary = `which appear`.strip
50
+ if binary.empty?
51
+ binary = Appear::MODULE_DIR.join('bin/appear').to_s
52
+ end
53
+
54
+ command = Appear::Util::CommandBuilder.new(binary).args(pid)
55
+
56
+ if config
57
+ command.flag('verbose', true) unless config.silent
58
+ command.flag('log-file', config.log_file) if config.log_file
59
+ command.flag('record-runs', true) if config.record_runs
60
+ end
61
+
62
+ command.to_s
63
+ end
11
64
  end
12
65
 
13
66
  require 'appear/config'
14
67
  require 'appear/instance'
68
+ require 'appear/util/command_builder'
data/scripts/console CHANGED
@@ -7,6 +7,7 @@
7
7
  require "bundler/setup"
8
8
  require "pry"
9
9
  require "appear"
10
+ require "appear/editor"
10
11
 
11
12
  def create_appear_instance
12
13
  config = Appear::Config.new
@@ -16,6 +17,13 @@ def create_appear_instance
16
17
  end
17
18
 
18
19
  instance, config = create_appear_instance
19
- puts "generated instance #{instance} from config #{config}"
20
+ services = instance.instance_variable_get('@all_services')
21
+ # nvim = Appear::Editor::Nvim.find_for_file(File.expand_path('.'))
22
+ # nvim = Appear::Editor::Nvim.new(Appear::Editor::Nvim.sockets.last, services)
23
+ nvim = Appear::Editor::Nvim.find_for_file(__FILE__, services)
24
+ ide = Appear::Editor::TmuxIde.new(services)
25
+ puts "generated instance = #{instance} from config = #{config}"
26
+ puts "connected nvim = #{nvim}"
27
+ puts "generated ide = #{ide}"
20
28
 
21
29
  binding.pry
@@ -27,6 +27,15 @@ var ScriptContext = this
27
27
  // -----------------------------------------------------------
28
28
  var PROGRAM_NAME = 'appear-macOS-helper'
29
29
  var Methods = {}
30
+ function delegateMethod(methodName, klass, fn) {
31
+ result = function() {
32
+ var instance = new klass();
33
+ return instance[fn].apply(instance, arguments)
34
+ }
35
+ result.name = methodName
36
+ Methods[methodName] = result
37
+ return result
38
+ }
30
39
 
31
40
  // entrypoint -------------------------------------------------
32
41
  // this is the main method of this script when it is called from the command line.
@@ -121,16 +130,22 @@ Iterm2.prototype.revealTty = function revealTty(tty) {
121
130
  return success;
122
131
  }
123
132
 
124
- Methods['iterm2_reveal_tty'] = function iterm2_reveal_tty(tty) {
125
- var iterm2 = new Iterm2()
126
- return iterm2.revealTty(tty)
127
- }
133
+ Iterm2.prototype.newWindow = function newWindow(command) {
134
+ var window = this.app.createWindowWithDefaultProfile({command: command})
135
+ var session = window.currentSession();
128
136
 
129
- Methods['iterm2_panes'] = function iterm2_panes() {
130
- var iterm2 = new Iterm2()
131
- return iterm2.panes()
137
+ return {
138
+ win: window,
139
+ tab: window.currentTab(),
140
+ session: session,
141
+ tty: session.tty(),
142
+ };
132
143
  }
133
144
 
145
+ delegateMethod('iterm2_reveal_tty', Iterm2, 'revealTty')
146
+ delegateMethod('iterm2_panes', Iterm2, 'panes')
147
+ delegateMethod('iterm2_new_window', Iterm2, 'newWindow')
148
+
134
149
  // -------------------------------------------------------------
135
150
  // Terminal.app library
136
151
 
@@ -168,15 +183,8 @@ Terminal.prototype.revealTty = function revealTty(tty) {
168
183
  return success
169
184
  }
170
185
 
171
- Methods['terminal_reveal_tty'] = function terminal_reveal_tty(tty) {
172
- var terminal = new Terminal()
173
- return terminal.revealTty(tty)
174
- }
175
-
176
- Methods['terminal_panes'] = function terminal_panes() {
177
- var terminal = new Terminal()
178
- return terminal.panes()
179
- }
186
+ delegateMethod('terminal_reveal_tty', Terminal, 'revealTty')
187
+ delegateMethod('terminal_panes', Terminal, 'panes')
180
188
 
181
189
  // for tests ----------------------------------------------
182
190
 
@@ -0,0 +1,167 @@
1
+ #!/usr/bin/env osascript
2
+ (*
3
+ * script: unix-dropper.applescript
4
+ * author: Jake Teton-Landis <just.1.jake@gmail.com>
5
+ * date: 2016-08-30
6
+ *
7
+ * when saved as an application (open in Script Editor, then choose
8
+ * File->Export...), this script can be used to process files or folders with a
9
+ * Unix script via drag-and-drop.
10
+ *
11
+ * You can customize the preferences of your Unix script by opening the
12
+ * application the regular way.
13
+ *
14
+ * Your script will be called as a sh function, so you can process $@ using
15
+ * `shift` or something.
16
+ *
17
+ * Heplful documentation for maintainers:
18
+ * Technical Note TN2065 - do shell script in AppleScript
19
+ * https://developer.apple.com/library/mac/technotes/tn2065/_index.html
20
+ *
21
+ * Here's the default script that this app will run, creaetd from the default
22
+ * property `user_unix_command`, defined below, as though the dropped files were
23
+ * "first", "second", "third".
24
+ *
25
+ * ```sh
26
+ * #!/bin/sh
27
+ * script-wrapper () {
28
+ * ARG_C='3'
29
+ * ARG_1='first'
30
+ * ARG_2='second'
31
+ * ARG_3='third'
32
+ * say "dropped $ARG_C files. first file: $ARG_0"
33
+ * }
34
+ * # we do this so you can use "$@"
35
+ * # and unix conventions if you're unix-y
36
+ * script-wrapper first second third
37
+ * ```
38
+ *)
39
+
40
+ -- property default_command : "say \"dropped $ARG_C files. first file: $ARG_0\""
41
+ property default_command : "PATH=\"/usr/local/bin:$PATH\" /usr/local/bin/appear --edit -- \"$@\""
42
+
43
+
44
+ --- this is a stored user preference.
45
+ --- this is the default, but it can be set as a preference in a .plist if this script is saved as an applicication
46
+ property user_unix_command : default_command
47
+
48
+ --- this function runs when the user opens the application by double-clicking it.
49
+ --- uer can adjust the user_unix_command by opening the application
50
+ on run
51
+ set quit_ to "Quit"
52
+ set reset to "Reset"
53
+ set save_ to "Save"
54
+ repeat
55
+ set ds to doc_string()
56
+ set d_res to display dialog ds buttons {quit_, reset, save_} default button save_ default answer user_unix_command
57
+ if the button returned of d_res is save_ then
58
+ set user_unix_command to the text returned of the result
59
+ end if
60
+ if the button returned of d_res is quit_ then
61
+ return "user quit"
62
+ end if
63
+ end repeat
64
+ end run
65
+
66
+ -- This droplet processes files dropped onto the applet
67
+ on open these_items
68
+ set as_strings to {}
69
+ repeat with cur in these_items
70
+ set cur_as_string to (POSIX path of cur) as string
71
+ copy cur_as_string to end of as_strings
72
+ end repeat
73
+ set the_command to unix_script(user_unix_command, as_strings)
74
+ log the_command
75
+ do shell script the_command
76
+ end open
77
+
78
+ --- here's how we end up building the shell script to call
79
+ --- the user's shell script with hella sweet args
80
+ on build_arg_vars(args)
81
+ -- MOAR SPEED
82
+ return {}
83
+ set argc to the count of args
84
+ set res to {set_env_var("ARG_C", argc)}
85
+
86
+ repeat with i from 1 to the count of args
87
+ set arg to item i of args
88
+ copy set_env_var("ARG_" & i, arg) to end of res
89
+ end repeat
90
+ res
91
+ end build_arg_vars
92
+
93
+ on build_script(user_script, args)
94
+ set fn_name to "wrapper_fn"
95
+ set open_fn to fn_name & " () {"
96
+ set close_fn to "}"
97
+ set call_fn to fn_name & " " & join_list(args, " ")
98
+ set comment to "# we do this so you can use \"$@\"
99
+ # and unix conventions if you're unix-y"
100
+
101
+ set res to build_arg_vars(args)
102
+
103
+ copy open_fn to beginning of res
104
+ copy user_script to end of res
105
+ copy close_fn to end of res
106
+ copy comment to end of res
107
+ copy call_fn to end of res
108
+ res
109
+ end build_script
110
+
111
+ on set_env_var(var, value)
112
+
113
+ set res to var & "=" & (the quoted form of ("" & value))
114
+ # display dialog res
115
+
116
+ return res
117
+
118
+ end set_env_var
119
+
120
+ on join_list(the_list, sep)
121
+ if (count of the_list) is 0 then
122
+ return ""
123
+ end if
124
+
125
+ if (count of the_list) is 1 then
126
+ return "" & item 1 of the_list
127
+ end if
128
+
129
+ set res to "" & item 1 of the_list
130
+
131
+ repeat with i from 2 to the count of the_list
132
+ set el to item i of the_list
133
+ set res to res & sep & el
134
+ end repeat
135
+ res
136
+ end join_list
137
+
138
+ on unix_script(user_command, args)
139
+ join_list(build_script(user_command, args), "
140
+ ")
141
+ end unix_script
142
+
143
+
144
+ -- returns string
145
+ on doc_string()
146
+ "Enter a unix command to run when this application should open a file. Your script has several environment variables availible, see below.
147
+
148
+ Substitutions availible:
149
+ \"$@\": All arguments, quoted
150
+ (all sh default environment variables)
151
+ $ARG_C: Total number of arguments
152
+ $ARG_1: First argument
153
+ $ARG_2: Second argument
154
+ etc...
155
+
156
+ Current command:
157
+ `" & user_unix_command & "`
158
+
159
+ Final script, given dropped files {first, second, third}:
160
+ ```sh
161
+ #!/bin/sh
162
+ " & unix_script(user_unix_command, {"first", "second", "third"}) & "
163
+ ```"
164
+ end doc_string
165
+
166
+ --- uncomment to test
167
+ -- unix_script("/Users/jake/src/foo $1 a/b $ARG_4 -- \"$@\"", {"foo", "bar"})
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appear
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Teton-Landis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-08-10 00:00:00.000000000 Z
11
+ date: 2016-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -114,8 +114,9 @@ files:
114
114
  - lib/appear/command.rb
115
115
  - lib/appear/config.rb
116
116
  - lib/appear/constants.rb
117
+ - lib/appear/editor.rb
118
+ - lib/appear/editor/nvim.rb
117
119
  - lib/appear/instance.rb
118
- - lib/appear/join.rb
119
120
  - lib/appear/lsof.rb
120
121
  - lib/appear/mac_os.rb
121
122
  - lib/appear/output.rb
@@ -123,11 +124,18 @@ files:
123
124
  - lib/appear/revealers.rb
124
125
  - lib/appear/runner.rb
125
126
  - lib/appear/service.rb
127
+ - lib/appear/terminal.rb
126
128
  - lib/appear/tmux.rb
129
+ - lib/appear/util.rb
130
+ - lib/appear/util/command_builder.rb
131
+ - lib/appear/util/join.rb
132
+ - lib/appear/util/memoizer.rb
133
+ - lib/appear/util/value_class.rb
127
134
  - screenshot.gif
128
135
  - scripts/console
129
136
  - scripts/setup
130
137
  - tools/macOS-helper.js
138
+ - tools/unix-dropper.applescript
131
139
  homepage: https://github.com/airbnb/appear
132
140
  licenses: []
133
141
  metadata: {}