pads 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +32 -2
- data/.ruby-version +1 -1
- data/Gemfile +6 -4
- data/Gemfile.lock +11 -1
- data/README.md +4 -6
- data/Rakefile +3 -3
- data/lib/pads/client/local_resolver.rb +75 -0
- data/lib/pads/client/pad.rb +94 -0
- data/lib/pads/client.rb +74 -0
- data/lib/pads/events/base.rb +8 -0
- data/lib/pads/events/button_clicked.rb +24 -0
- data/lib/pads/events/dispatcher.rb +34 -0
- data/lib/pads/events/files_dropped.rb +25 -0
- data/lib/pads/events/publisher.rb +39 -0
- data/lib/pads/events/subscription.rb +26 -0
- data/lib/pads/live_array.rb +29 -0
- data/lib/pads/mapper.rb +78 -0
- data/lib/pads/mutable_pad_state.rb +22 -0
- data/lib/pads/observable_pad_state.rb +40 -0
- data/lib/pads/pad.rb +82 -0
- data/lib/pads/pad_group.rb +76 -0
- data/lib/pads/pad_or_group.rb +35 -0
- data/lib/pads/pad_state.rb +45 -0
- data/lib/pads/pad_view.rb +44 -0
- data/lib/pads/provider/group_binding.rb +11 -0
- data/lib/pads/provider/tracking.rb +76 -0
- data/lib/pads/provider.rb +140 -0
- data/lib/pads/version.rb +1 -1
- data/lib/pads.rb +14 -3
- data/pads.gemspec +15 -13
- metadata +41 -5
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/pad_state'
|
4
|
+
|
5
|
+
module Pads
|
6
|
+
# Mutable value object for pad state.
|
7
|
+
class MutablePadState < PadState
|
8
|
+
attr_writer :title, :subtitle, :activity, :clickable, :buttons, :is_drop_target
|
9
|
+
|
10
|
+
def initialize(attrs = {})
|
11
|
+
super()
|
12
|
+
merge! DEFAULTS.merge(attrs)
|
13
|
+
end
|
14
|
+
|
15
|
+
def merge!(attrs)
|
16
|
+
attrs.each do |key, value|
|
17
|
+
__send__ :"#{key}=", value unless __send__(key) == value
|
18
|
+
end
|
19
|
+
self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/mutable_pad_state'
|
4
|
+
require 'observer'
|
5
|
+
|
6
|
+
module Pads
|
7
|
+
# A mutable pad state that can be observed.
|
8
|
+
class ObservablePadState < MutablePadState
|
9
|
+
include Observable
|
10
|
+
|
11
|
+
DEFAULTS.each_key do |key|
|
12
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
|
+
def #{key}=(*) # def title=(*)
|
14
|
+
compare { super } # compare { super }
|
15
|
+
end # end
|
16
|
+
RUBY
|
17
|
+
end
|
18
|
+
|
19
|
+
def merge!(*)
|
20
|
+
compare { super }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def compare
|
26
|
+
return yield if @is_comparing
|
27
|
+
|
28
|
+
@is_comparing = true
|
29
|
+
begin
|
30
|
+
old = to_h
|
31
|
+
result = yield
|
32
|
+
changed to_h != old
|
33
|
+
ensure
|
34
|
+
@is_comparing = false
|
35
|
+
end
|
36
|
+
notify_observers self
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/pads/pad.rb
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/pad_or_group'
|
4
|
+
require 'pads/pad_view'
|
5
|
+
require 'observer'
|
6
|
+
|
7
|
+
module Pads
|
8
|
+
# Controls a single pad.
|
9
|
+
class Pad
|
10
|
+
include PadOrGroup
|
11
|
+
|
12
|
+
attr_reader :events
|
13
|
+
|
14
|
+
def initialize(attrs = {})
|
15
|
+
@view_stack = []
|
16
|
+
@events = Events::Dispatcher.new
|
17
|
+
|
18
|
+
push_view PadView.new(attrs)
|
19
|
+
|
20
|
+
yield self if block_given?
|
21
|
+
end
|
22
|
+
|
23
|
+
def view
|
24
|
+
@view_stack.first
|
25
|
+
end
|
26
|
+
|
27
|
+
def push_view(new_view = PadView.new)
|
28
|
+
new_view = PadView[new_view]
|
29
|
+
change_view { @view_stack.unshift new_view }
|
30
|
+
yield new_view if block_given?
|
31
|
+
new_view
|
32
|
+
end
|
33
|
+
|
34
|
+
def pop_view
|
35
|
+
raise 'You cannot pop the top level view' if @view_stack.one?
|
36
|
+
|
37
|
+
change_view { @view_stack.shift }
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_view(new_view = PadView.new)
|
41
|
+
new_view = PadView[new_view]
|
42
|
+
push_view new_view
|
43
|
+
yield new_view
|
44
|
+
ensure
|
45
|
+
pop_view
|
46
|
+
end
|
47
|
+
|
48
|
+
def bind_events(source_dispatcher)
|
49
|
+
@subscriptions = source_dispatcher.publishers.keys.map do |event_name|
|
50
|
+
source_dispatcher.on(event_name) { |*args| view.events.dispatch event_name, *args }
|
51
|
+
end.freeze
|
52
|
+
end
|
53
|
+
|
54
|
+
def unbind_events
|
55
|
+
@subscriptions.each(&:unsubscribe)
|
56
|
+
@subscriptions = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def view_changed(*)
|
60
|
+
changed
|
61
|
+
notify_observers self
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def change_view
|
67
|
+
view&.delete_observer self
|
68
|
+
yield
|
69
|
+
view.add_observer self, :view_changed
|
70
|
+
view_changed
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
74
|
+
def compare
|
75
|
+
old = view
|
76
|
+
result = yield
|
77
|
+
changed old != view
|
78
|
+
notify_observers self
|
79
|
+
result
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'observer'
|
4
|
+
require 'pads/pad'
|
5
|
+
require 'pads/pad_or_group'
|
6
|
+
|
7
|
+
module Pads
|
8
|
+
# Represents a group/sequence of pads.
|
9
|
+
class PadGroup
|
10
|
+
include PadOrGroup
|
11
|
+
|
12
|
+
def initialize(members = [])
|
13
|
+
@members = []
|
14
|
+
@mutex = Mutex.new
|
15
|
+
|
16
|
+
batch { members.each(&method(:push)) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def insert(index, pad_or_group)
|
20
|
+
raise ArgumentError, 'Index out of range' unless (0..@members.length).cover? index
|
21
|
+
|
22
|
+
pad_or_group = PadOrGroup.coerce(pad_or_group)
|
23
|
+
pad_or_group.parent = self
|
24
|
+
batch { @members.insert index, pad_or_group }
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_at(index)
|
28
|
+
batch do
|
29
|
+
@members.delete_at(index).tap { |p| p.parent = nil } or
|
30
|
+
raise ArgumentError, 'Nothing to delete at the given index'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def swap(index_a, index_b)
|
35
|
+
raise ArgumentError, 'Index out of range' unless [index_a, index_b].all?(&(0..@members.length).method(:cover?))
|
36
|
+
raise ArgumentError, 'Indexes are identical' if index_a == index_b
|
37
|
+
|
38
|
+
batch do
|
39
|
+
@members[index_a], @members[index_b] = @members.values_at(index_b, index_a)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def batch
|
44
|
+
return yield if @mutex.owned?
|
45
|
+
|
46
|
+
result = @mutex.synchronize do
|
47
|
+
previous = @members.dup
|
48
|
+
yield.tap do
|
49
|
+
changed previous != @members
|
50
|
+
end
|
51
|
+
end
|
52
|
+
notify_observers self
|
53
|
+
result
|
54
|
+
end
|
55
|
+
|
56
|
+
def push(*args, &block)
|
57
|
+
return push Mapper.map(*args, &block) if block
|
58
|
+
|
59
|
+
batch do
|
60
|
+
args.each do |arg|
|
61
|
+
insert @members.length, arg
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Array<Pad, PadGroup>]
|
67
|
+
def members
|
68
|
+
@members.dup
|
69
|
+
end
|
70
|
+
|
71
|
+
def relay_notification(*args)
|
72
|
+
changed
|
73
|
+
notify_observers(*args)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pads
|
4
|
+
# Common functionality for Pads and PadGroups
|
5
|
+
module PadOrGroup
|
6
|
+
include Observable
|
7
|
+
|
8
|
+
def self.coerce(object)
|
9
|
+
case object
|
10
|
+
when Pad, PadGroup
|
11
|
+
object
|
12
|
+
when Hash
|
13
|
+
Pad.new object
|
14
|
+
when Array
|
15
|
+
PadGroup.new object.map(&method(:coerce))
|
16
|
+
when nil
|
17
|
+
PadGroup.new
|
18
|
+
else
|
19
|
+
raise ArgumentError, "Cannot coerce #{object.inspect} to a pad or group of pads"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
alias == equal?
|
24
|
+
|
25
|
+
attr_reader :parent
|
26
|
+
|
27
|
+
def parent=(new_parent)
|
28
|
+
return if parent == new_parent
|
29
|
+
|
30
|
+
delete_observer parent if parent
|
31
|
+
@parent = new_parent
|
32
|
+
add_observer parent, :relay_notification if parent
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pads
|
4
|
+
# Immutable value object for pad state.
|
5
|
+
class PadState
|
6
|
+
DEFAULTS = {
|
7
|
+
title: '',
|
8
|
+
subtitle: '',
|
9
|
+
activity: false,
|
10
|
+
clickable: false,
|
11
|
+
buttons: [],
|
12
|
+
is_drop_target: false
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
attr_reader :title, :subtitle, :activity, :clickable, :buttons, :is_drop_target
|
16
|
+
|
17
|
+
def initialize(attrs = {})
|
18
|
+
DEFAULTS.merge(attrs).each do |key, value|
|
19
|
+
raise ArgumentError, 'Unknown key' unless DEFAULTS.key?(key.to_sym)
|
20
|
+
|
21
|
+
instance_variable_set :"@#{key}", value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
DEFAULTS.keys.to_h do |key|
|
27
|
+
[key, instance_variable_get(:"@#{key}")]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge(attrs)
|
32
|
+
self.class.new to_h.merge(attrs)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
return false unless other.is_a? PadState
|
37
|
+
|
38
|
+
to_h == other.to_h
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.[](obj)
|
42
|
+
obj.is_a?(PadState) ? obj : PadState.new(obj)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/observable_pad_state'
|
4
|
+
|
5
|
+
module Pads
|
6
|
+
# Represents a stackable view of a pad
|
7
|
+
class PadView < ObservablePadState
|
8
|
+
attr_reader :events
|
9
|
+
|
10
|
+
def self.[](obj)
|
11
|
+
obj.is_a?(PadView) ? obj : new(obj)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(attrs = {})
|
15
|
+
super
|
16
|
+
|
17
|
+
@events = Events::Dispatcher.new
|
18
|
+
@button_handlers = []
|
19
|
+
|
20
|
+
@events.on :button_clicked do |event|
|
21
|
+
@button_handlers[event.index]&.call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_click(&handler)
|
26
|
+
@events.on :pad_clicked, &handler
|
27
|
+
self.clickable = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_drop(path_pattern = nil, &handler)
|
31
|
+
@events.on :files_dropped do |file_dropped|
|
32
|
+
file_dropped.files.each do |file|
|
33
|
+
handler.call(file.path) if path_pattern.nil? || file.path.match?(path_pattern)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
self.is_drop_target = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def button(label, &handler)
|
40
|
+
@button_handlers << handler
|
41
|
+
self.buttons += [label]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pads
|
4
|
+
class Provider
|
5
|
+
# Represents a server binding for a pad group. Just an array, but uses object IDs for comparisons, so that e.g.
|
6
|
+
# two empty groups are not considered equal.
|
7
|
+
class GroupBinding < Array
|
8
|
+
alias == equal?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/provider/group_binding'
|
4
|
+
|
5
|
+
module Pads
|
6
|
+
class Provider
|
7
|
+
# Supplemental methods for the Provider class
|
8
|
+
module Tracking
|
9
|
+
protected
|
10
|
+
|
11
|
+
def pad?(pad_or_group)
|
12
|
+
case pad_or_group
|
13
|
+
when Pad, Client::Pad
|
14
|
+
true
|
15
|
+
when PadOrGroup, GroupBinding
|
16
|
+
false
|
17
|
+
else
|
18
|
+
raise ArgumentError, 'Expected a pad or group'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def pad_range_for_binding(binding)
|
23
|
+
return [binding, binding] if pad? binding
|
24
|
+
|
25
|
+
flat = binding.flatten
|
26
|
+
flat.empty? ? nil : [flat.first, flat.last]
|
27
|
+
end
|
28
|
+
|
29
|
+
def subtract_pad_range(partial_group_binding, range_to_remove)
|
30
|
+
indexes_to_remove = range_to_remove.map(&partial_group_binding.method(:index))
|
31
|
+
remaining = partial_group_binding[
|
32
|
+
indexes_to_remove.first.zero? ? (indexes_to_remove.last + 1).. : 0...indexes_to_remove.first
|
33
|
+
].flatten
|
34
|
+
remaining.empty? ? nil : [remaining.first, remaining.last]
|
35
|
+
end
|
36
|
+
|
37
|
+
def walk(pad_group_binding, skip_group_bindings: true, &block)
|
38
|
+
yield pad_group_binding unless skip_group_bindings
|
39
|
+
|
40
|
+
pad_group_binding.each do |binding|
|
41
|
+
if pad? binding
|
42
|
+
block.call binding
|
43
|
+
else
|
44
|
+
walk binding, skip_group_bindings: skip_group_bindings, &block
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
yield nil unless skip_group_bindings
|
49
|
+
end
|
50
|
+
|
51
|
+
def create_options(root_binding, pad_group_binding) # rubocop:disable Metrics/MethodLength
|
52
|
+
create_options = {}
|
53
|
+
phase = 1 # 1 = before this group, 2 = in this group, 3 = after this group
|
54
|
+
|
55
|
+
walk root_binding, skip_group_bindings: false do |binding|
|
56
|
+
if binding == pad_group_binding
|
57
|
+
break if create_options[:after]
|
58
|
+
|
59
|
+
phase = 2
|
60
|
+
elsif binding.nil?
|
61
|
+
phase = 3
|
62
|
+
elsif binding.is_a? GroupBinding
|
63
|
+
next
|
64
|
+
elsif phase == 1
|
65
|
+
create_options[:after] = binding
|
66
|
+
elsif phase == 3
|
67
|
+
create_options[:before] = binding
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
create_options
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pads/client'
|
4
|
+
require 'pads/pad_group'
|
5
|
+
require 'pads/provider/tracking'
|
6
|
+
|
7
|
+
module Pads
|
8
|
+
# The base class for a long-running process that provides pads to the Pads app.
|
9
|
+
class Provider
|
10
|
+
include Tracking
|
11
|
+
|
12
|
+
def initialize(client = Client.new)
|
13
|
+
@client = client
|
14
|
+
@root = PadGroup.new
|
15
|
+
@mutex = Mutex.new
|
16
|
+
|
17
|
+
@bindings = { @root => GroupBinding.new }.compare_by_identity
|
18
|
+
@bindings_inverse = @bindings.invert.compare_by_identity
|
19
|
+
|
20
|
+
@root.add_observer self, :pad_or_group_update
|
21
|
+
end
|
22
|
+
|
23
|
+
def push(*args, &block)
|
24
|
+
@root.push(*args, &block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait
|
28
|
+
@client.wait
|
29
|
+
end
|
30
|
+
|
31
|
+
def pad_or_group_update(pad_or_group)
|
32
|
+
return @mutex.synchronize { pad_or_group_update pad_or_group } unless @mutex.owned?
|
33
|
+
|
34
|
+
if pad? pad_or_group
|
35
|
+
pad_update pad_or_group
|
36
|
+
else
|
37
|
+
pad_group_update pad_or_group
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# @param [Pads::Pad] pad
|
44
|
+
def pad_update(pad)
|
45
|
+
binding_for(pad).merge! pad.view.to_h
|
46
|
+
end
|
47
|
+
|
48
|
+
def pad_group_update(pad_group)
|
49
|
+
pad_group_binding = binding_for(pad_group)
|
50
|
+
remove_old_members pad_group, pad_group_binding
|
51
|
+
sort_members pad_group, pad_group_binding
|
52
|
+
add_new_members pad_group, pad_group_binding
|
53
|
+
end
|
54
|
+
|
55
|
+
def remove_old_members(pad_group, pad_group_binding)
|
56
|
+
expected = Set.new(pad_group.members)
|
57
|
+
pad_group_binding.each.with_index.reverse_each do |member_binding, index|
|
58
|
+
member = pad_or_group_for(member_binding)
|
59
|
+
next if expected.include? member
|
60
|
+
|
61
|
+
destroy member
|
62
|
+
pad_group_binding.delete_at index
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def sort_members(pad_group, pad_group_binding)
|
67
|
+
return unless pad_group_binding.length > 1
|
68
|
+
|
69
|
+
expected = pad_group.members.map(&@bindings.method(:[])) & pad_group_binding
|
70
|
+
|
71
|
+
(0..(pad_group_binding.length - 2)).each do |i|
|
72
|
+
a = expected[i]
|
73
|
+
b = pad_group_binding[i]
|
74
|
+
|
75
|
+
swap pad_group_binding, i, pad_group_binding.index(a) unless b == a
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_new_members(pad_group, pad_group_binding)
|
80
|
+
create_options = self.create_options(binding_for(@root), pad_group_binding)
|
81
|
+
|
82
|
+
pad_group.members.each.with_index do |member, index|
|
83
|
+
binding = @bindings[member] ||
|
84
|
+
create_binding(member, create_options).tap { |b| pad_group_binding.insert index, b }
|
85
|
+
|
86
|
+
create_options = { after: binding } if binding.is_a? Client::Pad
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def create_binding(member, create_options)
|
91
|
+
is_pad = pad?(member)
|
92
|
+
binding = is_pad ? @client.create_pad(**create_options) : GroupBinding.new
|
93
|
+
|
94
|
+
@bindings[member] = binding
|
95
|
+
@bindings_inverse[binding] = member
|
96
|
+
|
97
|
+
member.bind_events(binding.events) if is_pad
|
98
|
+
|
99
|
+
pad_or_group_update(member)
|
100
|
+
|
101
|
+
binding
|
102
|
+
end
|
103
|
+
|
104
|
+
def swap(pad_group_binding, index_a, index_b) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
105
|
+
bindings = [index_a, index_b].map(&pad_group_binding.method(:fetch))
|
106
|
+
|
107
|
+
pad_group_binding[index_b], pad_group_binding[index_a] = bindings
|
108
|
+
|
109
|
+
ranges = bindings.map(&method(:pad_range_for_binding)).sort_by { |range| range.nil? ? 1 : 0 }
|
110
|
+
return if ranges.all?(&:nil?)
|
111
|
+
|
112
|
+
ranges[1] ||= subtract_pad_range(pad_group_binding[index_a..index_b], ranges[0])
|
113
|
+
return if ranges[1].nil?
|
114
|
+
|
115
|
+
@client.swap_pads(*ranges)
|
116
|
+
end
|
117
|
+
|
118
|
+
def binding_for(pad_or_group)
|
119
|
+
@bindings.fetch(pad_or_group) { raise "No server binding for #{pad_or_group}" }
|
120
|
+
end
|
121
|
+
|
122
|
+
def pad_or_group_for(binding)
|
123
|
+
@bindings_inverse.fetch(binding)
|
124
|
+
end
|
125
|
+
|
126
|
+
def destroy(pad_or_group)
|
127
|
+
binding = binding_for(pad_or_group)
|
128
|
+
|
129
|
+
if pad? pad_or_group
|
130
|
+
pad_or_group.unbind_events
|
131
|
+
binding.destroy
|
132
|
+
else
|
133
|
+
binding.each(&method(:destroy))
|
134
|
+
end
|
135
|
+
|
136
|
+
@bindings.delete pad_or_group
|
137
|
+
@bindings_inverse.delete binding
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/pads/version.rb
CHANGED
data/lib/pads.rb
CHANGED
@@ -1,8 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'pads/version'
|
4
|
+
require 'pads/provider'
|
5
|
+
require 'pads/mapper'
|
6
|
+
require 'pads/live_array'
|
4
7
|
|
8
|
+
# Provide pads to the Pads macOS app.
|
5
9
|
module Pads
|
6
|
-
|
7
|
-
|
10
|
+
def self.provide(*args)
|
11
|
+
provider = Provider.new(*args)
|
12
|
+
yield provider
|
13
|
+
provider.wait
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.pad(*args, &block)
|
17
|
+
Pad.new *args, &block
|
18
|
+
end
|
8
19
|
end
|
data/pads.gemspec
CHANGED
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative
|
3
|
+
require_relative 'lib/pads/version'
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
6
|
+
spec.name = 'pads'
|
7
7
|
spec.version = Pads::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
8
|
+
spec.authors = ['Neil E. Pearson']
|
9
|
+
spec.email = ['neil@pearson.sydney']
|
10
10
|
|
11
|
-
spec.summary =
|
12
|
-
spec.description =
|
13
|
-
spec.homepage =
|
14
|
-
spec.license =
|
15
|
-
spec.required_ruby_version =
|
11
|
+
spec.summary = 'Ruby client for the Pads macOS app'
|
12
|
+
spec.description = 'Ruby client for the Pads macOS app'
|
13
|
+
spec.homepage = 'https://github.com/hx/pads'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 2.6.3'
|
16
16
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
19
19
|
|
20
20
|
# Specify which files should be added to the gem when it is released.
|
21
21
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
@@ -24,13 +24,15 @@ Gem::Specification.new do |spec|
|
|
24
24
|
(f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
25
25
|
end
|
26
26
|
end
|
27
|
-
spec.bindir =
|
27
|
+
spec.bindir = 'exe'
|
28
28
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
-
spec.require_paths = [
|
29
|
+
spec.require_paths = ['lib']
|
30
30
|
|
31
31
|
# Uncomment to register a new dependency of your gem
|
32
32
|
# spec.add_dependency "example-gem", "~> 1.0"
|
33
|
+
spec.add_dependency 'interop', '~> 0.3.1'
|
33
34
|
|
34
35
|
# For more information and examples about making a new gem, check out our
|
35
36
|
# guide at: https://bundler.io/guides/creating_gem.html
|
37
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
36
38
|
end
|