weechat 0.0.1
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/AUTHORS.md +11 -0
- data/COPYING +674 -0
- data/ChangeLog +0 -0
- data/README.md +296 -0
- data/Rakefile +55 -0
- data/TODO +188 -0
- data/lib/weechat.rb +229 -0
- data/lib/weechat/blankslate.rb +13 -0
- data/lib/weechat/buffer.rb +559 -0
- data/lib/weechat/callback.rb +11 -0
- data/lib/weechat/color.rb +41 -0
- data/lib/weechat/command.rb +66 -0
- data/lib/weechat/exceptions.rb +31 -0
- data/lib/weechat/hook.rb +93 -0
- data/lib/weechat/hooks.rb +8 -0
- data/lib/weechat/hooks/command_run.rb +30 -0
- data/lib/weechat/hooks/config.rb +11 -0
- data/lib/weechat/hooks/print.rb +14 -0
- data/lib/weechat/info.rb +14 -0
- data/lib/weechat/infolist.rb +32 -0
- data/lib/weechat/input.rb +45 -0
- data/lib/weechat/line.rb +60 -0
- data/lib/weechat/modifier.rb +24 -0
- data/lib/weechat/option.rb +46 -0
- data/lib/weechat/plugin.rb +106 -0
- data/lib/weechat/pointer.rb +28 -0
- data/lib/weechat/process.rb +32 -0
- data/lib/weechat/properties.rb +312 -0
- data/lib/weechat/property.rb +47 -0
- data/lib/weechat/rubyext/array.rb +22 -0
- data/lib/weechat/rubyext/boolean.rb +17 -0
- data/lib/weechat/rubyext/float.rb +9 -0
- data/lib/weechat/rubyext/integer.rb +9 -0
- data/lib/weechat/rubyext/object.rb +10 -0
- data/lib/weechat/rubyext/string.rb +142 -0
- data/lib/weechat/script.rb +85 -0
- data/lib/weechat/script/config.rb +146 -0
- data/lib/weechat/server.rb +137 -0
- data/lib/weechat/terminal.rb +8 -0
- data/lib/weechat/timer.rb +56 -0
- data/lib/weechat/utilities.rb +47 -0
- data/lib/weechat/window.rb +103 -0
- metadata +96 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
module Weechat
|
2
|
+
# Note: As opposed to plain WeeChat, we properly parse arguments
|
3
|
+
# given to a command, like a shell would.
|
4
|
+
class Modifier < Hook
|
5
|
+
attr_reader :modifier
|
6
|
+
def initialize(modifier, &callback)
|
7
|
+
super
|
8
|
+
@modifier = modifier.to_s
|
9
|
+
@callback = callback # we do not use the Callback class
|
10
|
+
# here because we need the return
|
11
|
+
# value of the callback
|
12
|
+
@ptr = Weechat.hook_modifier(modifier, "modifier_callback", id.to_s)
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :exec, :call
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def call(modifier, data, string)
|
19
|
+
Weechat.hook_modifier_exec(modifier.to_s, data.to_s, string.to_s)
|
20
|
+
end
|
21
|
+
alias_method :exec, :call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Weechat
|
2
|
+
class Option < Blankslate
|
3
|
+
@options = [] # we can't use a Hash because hashing
|
4
|
+
# blankslate... breaks things
|
5
|
+
|
6
|
+
|
7
|
+
def self.options
|
8
|
+
@options
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(config, option)
|
12
|
+
self.__class__.options << [config, option, self]
|
13
|
+
@old_obj = config.__get(option)
|
14
|
+
@config = config
|
15
|
+
@option = option
|
16
|
+
@frozen = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def __freeze__
|
20
|
+
@frozen = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(m, *args, &block)
|
24
|
+
if @frozen
|
25
|
+
obj = @old_obj
|
26
|
+
else
|
27
|
+
obj = @config.__get(@option)
|
28
|
+
end
|
29
|
+
ret = obj.__send__(m, *args, &block)
|
30
|
+
|
31
|
+
if (@old_obj != obj) && !@frozen
|
32
|
+
@config.set!(@option, obj)
|
33
|
+
end
|
34
|
+
|
35
|
+
unless @frozen
|
36
|
+
begin
|
37
|
+
@old_obj = obj.dup
|
38
|
+
rescue TypeError
|
39
|
+
@old_obj = obj
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
ret
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Weechat
|
2
|
+
# == Gettable properties
|
3
|
+
#
|
4
|
+
# [filename] Filename of the plugin
|
5
|
+
# [handle] ?
|
6
|
+
# [name] Name of the plugin
|
7
|
+
# [description] Description of the plugin
|
8
|
+
# [author] Author of the plugin
|
9
|
+
# [version] Version of the plugin
|
10
|
+
# [license] Licence of the plugin
|
11
|
+
# [charset] ?
|
12
|
+
# [debug?] ?
|
13
|
+
class Plugin
|
14
|
+
include Weechat::Pointer
|
15
|
+
extend Weechat::Properties
|
16
|
+
|
17
|
+
@mappings = {
|
18
|
+
:licence => :license,
|
19
|
+
:debug? => :debug,
|
20
|
+
}
|
21
|
+
|
22
|
+
@transformations = {
|
23
|
+
[:debug] => lambda {|v| Weechat.integer_to_bool(v) },
|
24
|
+
}
|
25
|
+
|
26
|
+
init_properties
|
27
|
+
|
28
|
+
class << self
|
29
|
+
def find_by_name(name)
|
30
|
+
if name.nil? or name.empty? or name == "core"
|
31
|
+
return Plugin.new("")
|
32
|
+
end
|
33
|
+
plugins.find {|plugin| plugin.name == name}
|
34
|
+
end
|
35
|
+
alias_method :find, :find_by_name
|
36
|
+
|
37
|
+
def plugins
|
38
|
+
plugins = [Plugin.new("")]
|
39
|
+
Weechat::Infolist.parse("plugin").each do |plugin|
|
40
|
+
plugins << Plugin.new(plugin[:pointer])
|
41
|
+
end
|
42
|
+
plugins
|
43
|
+
end
|
44
|
+
alias_method :all, :plugins
|
45
|
+
|
46
|
+
# Loads a plugin.
|
47
|
+
#
|
48
|
+
# @return [void]
|
49
|
+
def load(name)
|
50
|
+
Weechat.exec("/plugin load #{name}")
|
51
|
+
end
|
52
|
+
|
53
|
+
# Reloads all plugins.
|
54
|
+
#
|
55
|
+
# Note: This will not reload the ruby plugin.
|
56
|
+
#
|
57
|
+
# @return [Array<Plugin>] All plugins that have been reloaded.
|
58
|
+
def reload_all
|
59
|
+
plugins = all.select{|plugin| plugin.name != "ruby"}
|
60
|
+
plugins.each {|plugin| plugin.reload}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def name
|
65
|
+
Weechat.plugin_get_name(@ptr)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Unloads the plugin.
|
69
|
+
#
|
70
|
+
# @param [Boolean] force If the plugin to be unloaded is "ruby",
|
71
|
+
# +force+ has to be true.
|
72
|
+
# @return [Boolean] true if we attempted to unload the plugin
|
73
|
+
def unload(force = false)
|
74
|
+
if name == "ruby" and !force
|
75
|
+
Weechat.puts "Won't unload the ruby plugin unless you force it."
|
76
|
+
false
|
77
|
+
else
|
78
|
+
Weechat.exec("/plugin unload #{name}")
|
79
|
+
true
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Reload the plugin.
|
84
|
+
#
|
85
|
+
# @param [Boolean] force If the plugin to be reloaded is "ruby", +force+ has to be true.
|
86
|
+
# @return [Boolean] true if we attempted to reload the plugin
|
87
|
+
def reload(force = false)
|
88
|
+
if name == "ruby" and !force
|
89
|
+
Weechat.puts "Won't reload the ruby plugin unless you force it."
|
90
|
+
else
|
91
|
+
Weechat.exec("/plugin reload #{name}")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns an array of all scripts loaded by this plugin.
|
96
|
+
#
|
97
|
+
# @return [Array<Script>]
|
98
|
+
def scripts
|
99
|
+
scripts = []
|
100
|
+
Infolist.parse("#{name}_script").each do |script|
|
101
|
+
scripts << Script.new(script[:pointer], self)
|
102
|
+
end
|
103
|
+
scripts
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Weechat
|
2
|
+
module Pointer
|
3
|
+
attr_reader :ptr
|
4
|
+
alias_method :pointer, :ptr
|
5
|
+
|
6
|
+
def to_s
|
7
|
+
@ptr
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(ptr)
|
11
|
+
@ptr = ptr
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
@ptr == other.ptr
|
16
|
+
end
|
17
|
+
alias_method :eql?, "=="
|
18
|
+
alias_method :equal?, "=="
|
19
|
+
|
20
|
+
def hash
|
21
|
+
@ptr.hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
sprintf "#<%s:0x%x @ptr=%p>", self.class, object_id << 1, @ptr
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Weechat
|
2
|
+
class Process < Hook
|
3
|
+
# Returns a new instance of Process
|
4
|
+
#
|
5
|
+
# @param [String] command Command to execute
|
6
|
+
# @param [Integer] timeout Timeout after which to terminate the process
|
7
|
+
# @param [Boolean] collect If true, buffer output until process ends
|
8
|
+
|
9
|
+
attr_reader :collect
|
10
|
+
def initialize(command, timeout = 0, collect = false, &callback)
|
11
|
+
super
|
12
|
+
@command = command
|
13
|
+
@collect = collect
|
14
|
+
@stdout, @stderr = [], []
|
15
|
+
@callback = Callback.new(callback)
|
16
|
+
@ptr = Weechat.hook_process(command, timeout, "process_callback", id.to_s)
|
17
|
+
end
|
18
|
+
|
19
|
+
def stdout
|
20
|
+
@stdout.join("")
|
21
|
+
end
|
22
|
+
|
23
|
+
def stderr
|
24
|
+
@stderr.join("")
|
25
|
+
end
|
26
|
+
|
27
|
+
def buffer(stdout, stderr)
|
28
|
+
@stdout << stdout
|
29
|
+
@stderr << stderr
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
module Weechat
|
2
|
+
module Properties
|
3
|
+
module ClassMethods
|
4
|
+
# Returns all known properties.
|
5
|
+
#
|
6
|
+
# @return [Array<Symbol>] The properties
|
7
|
+
def known_properties
|
8
|
+
@known_integer_properties + @known_string_properties
|
9
|
+
end
|
10
|
+
|
11
|
+
def known_integer_properties
|
12
|
+
@known_integer_properties
|
13
|
+
end
|
14
|
+
|
15
|
+
def known_string_properties
|
16
|
+
@known_string_properties
|
17
|
+
end
|
18
|
+
|
19
|
+
def settable_properties
|
20
|
+
@settable_properties
|
21
|
+
end
|
22
|
+
|
23
|
+
def transformations
|
24
|
+
@transformations
|
25
|
+
end
|
26
|
+
|
27
|
+
def rtransformations
|
28
|
+
@rtransformations
|
29
|
+
end
|
30
|
+
|
31
|
+
def mappings
|
32
|
+
@mappings
|
33
|
+
end
|
34
|
+
|
35
|
+
def type
|
36
|
+
@type
|
37
|
+
end
|
38
|
+
|
39
|
+
def init_properties
|
40
|
+
@known_string_properties ||= [].freeze
|
41
|
+
@known_integer_properties ||= [].freeze
|
42
|
+
@settable_properties ||= [].freeze
|
43
|
+
@transformations ||= {}.freeze
|
44
|
+
@rtransformations ||= {}.freeze
|
45
|
+
@mappings ||= {}.freeze
|
46
|
+
|
47
|
+
@type = self.name.downcase.split("::").last
|
48
|
+
|
49
|
+
# this defines all the getter methods
|
50
|
+
known_properties.each do |property|
|
51
|
+
define_method(property) { get_property(property) }
|
52
|
+
end
|
53
|
+
|
54
|
+
# this defined all the setter methods
|
55
|
+
@settable_properties.each do |property|
|
56
|
+
define_method(property + '=') {|v| set_property(property, v, true) }
|
57
|
+
end
|
58
|
+
|
59
|
+
# this adds a few aliases to make interfaces more rubyish
|
60
|
+
@mappings.each do |key, value|
|
61
|
+
if respond_to?(value)
|
62
|
+
# it is a string/integer property
|
63
|
+
alias_method key, value
|
64
|
+
else
|
65
|
+
# it is an infolist property
|
66
|
+
define_method(key) do |*args|
|
67
|
+
__send__(value, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
InstanceMethods.alias_methods(@type)
|
74
|
+
include InstanceMethods
|
75
|
+
end
|
76
|
+
|
77
|
+
def apply_transformation(property, value)
|
78
|
+
Utilities.apply_transformation(property, value, @transformations)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module InstanceMethods
|
83
|
+
# Get a property. Transformations, if appropriate, will be applied to the value
|
84
|
+
# before returning it. This means that e.g. 0 and 1 might be turned into false and true.
|
85
|
+
#
|
86
|
+
# @raise [Exception::UnknownProperty]
|
87
|
+
# @return [String, Number, Boolean]
|
88
|
+
# @see #get_integer_property
|
89
|
+
# @see #get_string_property
|
90
|
+
# @see #get_infolist_property
|
91
|
+
# @see #set_property
|
92
|
+
def get_property(property)
|
93
|
+
raise Exception::UnknownProperty.new(property) unless valid_property?(property)
|
94
|
+
case ret = __get_property(property)
|
95
|
+
when true, false, nil
|
96
|
+
ret
|
97
|
+
else
|
98
|
+
Property.new(self, property)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# @private
|
103
|
+
def __get_property(property)
|
104
|
+
property = property.to_s
|
105
|
+
if valid_property?(property, :integer)
|
106
|
+
v = get_integer_property(property)
|
107
|
+
elsif valid_property?(property, :string)
|
108
|
+
v = get_string_property(property)
|
109
|
+
elsif valid_property?(property, :infolist)
|
110
|
+
v = get_infolist_property(property)
|
111
|
+
else
|
112
|
+
raise Exception::UnknownProperty.new(property)
|
113
|
+
end
|
114
|
+
|
115
|
+
return self.class.apply_transformation(property, v)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns an integer property.
|
119
|
+
#
|
120
|
+
# @raise [Exception::UnknownProperty]
|
121
|
+
# @return [Number]
|
122
|
+
# @see #get_integer
|
123
|
+
# @see #get_property
|
124
|
+
# @see #get_string_property
|
125
|
+
# @see #get_infolist_property
|
126
|
+
def get_integer_property(property)
|
127
|
+
property = property.to_s
|
128
|
+
raise Exception::UnknownProperty.new(property) unless valid_property?(property, :integer)
|
129
|
+
get_integer(property)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns an integer property, not doing any checks.
|
133
|
+
#
|
134
|
+
# @return [Number]
|
135
|
+
# @see #get_integer_property
|
136
|
+
# @see #get_string
|
137
|
+
# @see #get_property
|
138
|
+
def get_integer(property)
|
139
|
+
Weechat.__send__("#{self.class.type}_get_integer", @ptr, property.to_s).to_i
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a string property.
|
143
|
+
#
|
144
|
+
# @raise [Exception::UnknownProperty]
|
145
|
+
# @return [String]
|
146
|
+
# @see #get_string
|
147
|
+
# @see #get_property
|
148
|
+
# @see #get_integer_property
|
149
|
+
# @see #set_string_property
|
150
|
+
def get_string_property(property)
|
151
|
+
property = property.to_s
|
152
|
+
raise Exception::UnknownProperty.new(property) unless valid_property?(property, :string)
|
153
|
+
get_string(property)
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns a string property, not doing any checks.
|
157
|
+
#
|
158
|
+
# @return [String]
|
159
|
+
# @see #get_string_property
|
160
|
+
# @see #get_property
|
161
|
+
# @see #get_integer
|
162
|
+
# @see #set_string_property
|
163
|
+
def get_string(property)
|
164
|
+
Weechat.__send__("#{self.class.type}_get_string", @ptr, property.to_s)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Returns a hash representation of the associated infolist.
|
168
|
+
#
|
169
|
+
# @return [Hash{Symbol => Object}] All properties in the infolist
|
170
|
+
def get_infolist
|
171
|
+
Weechat::Infolist.parse(self.class.type, @ptr)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Returns a property obtained by an infolist.
|
175
|
+
#
|
176
|
+
# @raise [Exception::UnknownProperty]
|
177
|
+
# @return [String]
|
178
|
+
# @see #get_property
|
179
|
+
# @see #get_string_property
|
180
|
+
# @see #get_integer_property
|
181
|
+
def get_infolist_property(property)
|
182
|
+
property = property.to_sym
|
183
|
+
values = get_infolist.first
|
184
|
+
raise Exception::UnknownProperty.new(property.to_s) unless values[property]
|
185
|
+
values[property]
|
186
|
+
end
|
187
|
+
|
188
|
+
# Checks if a property can be set.
|
189
|
+
#
|
190
|
+
# @return [Boolean]
|
191
|
+
# @see #valid_property?
|
192
|
+
# @see #set_property
|
193
|
+
def settable_property?(property)
|
194
|
+
set_method = "#{self.class.type}_set"
|
195
|
+
return false unless Weechat.respond_to?(set_method)
|
196
|
+
|
197
|
+
property = property.to_s
|
198
|
+
self.class.settable_properties.include?(property)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Sets a property. Transformations, if appropriate, will be applied to the value
|
202
|
+
# before setting it. This means that e.g. true and false will be turned into 1 and 0.
|
203
|
+
#
|
204
|
+
# @raise [Exception::UnsettableProperty]
|
205
|
+
# @return [String, Integer] The value after if has been transformed
|
206
|
+
# @see #set_string_property
|
207
|
+
# @see #set
|
208
|
+
def set_property(property, v, freeze = false)
|
209
|
+
property = property.to_s
|
210
|
+
raise Exception::UnsettableProperty.new(property) unless settable_property?(property)
|
211
|
+
v = Utilities.apply_transformation(property, v, self.class.rtransformations)
|
212
|
+
|
213
|
+
set(property, v)
|
214
|
+
if freeze
|
215
|
+
Property.properties.each do |prop|
|
216
|
+
if prop[0..1] == [@ptr, property]
|
217
|
+
prop[2].__freeze__
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Sets a string property, not applying any transformations.
|
224
|
+
#
|
225
|
+
# @raise [Exception::UnsettableProperty]
|
226
|
+
# @return [String] The value
|
227
|
+
# @see #set_property
|
228
|
+
# @see #set
|
229
|
+
def set_string_property(property, v)
|
230
|
+
property = property.to_s
|
231
|
+
raise Exception::UnsettableProperty.new(property) unless settable_property?(property)
|
232
|
+
set(property, v)
|
233
|
+
end
|
234
|
+
|
235
|
+
# Sets a property, not doing any checks or conversions whatsoever.
|
236
|
+
#
|
237
|
+
# @return [Object] The value
|
238
|
+
# @see #set_property
|
239
|
+
# @see #set_string_property
|
240
|
+
def set(property, value)
|
241
|
+
set_method = "#{self.class.type}_set"
|
242
|
+
raise CannotSetProperties unless Weechat.respond_to?(set_method)
|
243
|
+
Weechat.__send__(set_method, @ptr, property.to_s, value.to_s)
|
244
|
+
value
|
245
|
+
end
|
246
|
+
|
247
|
+
# Checks if a property is valid. That is, if get_(integer|string|infolist)_property are
|
248
|
+
# able to return a value.
|
249
|
+
#
|
250
|
+
# @param [#to_s] property The name of the property
|
251
|
+
# @param [Symbol] type The type of properties to check for.
|
252
|
+
# Can be one of :all, :string, :integer, :localvar or :infolist
|
253
|
+
# @return [Boolean]
|
254
|
+
# @see #settable_property?
|
255
|
+
# @see #get_property
|
256
|
+
def valid_property?(property, type = :all)
|
257
|
+
property = property.to_s
|
258
|
+
case type
|
259
|
+
when :all
|
260
|
+
valid_property?(property, :string) or
|
261
|
+
valid_property?(property, :integer) or
|
262
|
+
valid_property?(property, :localvar) or
|
263
|
+
valid_property?(property, :infolist)
|
264
|
+
when :string
|
265
|
+
self.class.known_string_properties.include?(property) or valid_property?(property, :localvar)
|
266
|
+
when :integer
|
267
|
+
self.class.known_integer_properties.include?(property)
|
268
|
+
when :localvar
|
269
|
+
property =~ /^localvar_.+$/
|
270
|
+
when :infolist
|
271
|
+
get_infolist.first[property.to_sym]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# method_missing returns buffer local variables.
|
276
|
+
#
|
277
|
+
# @return [String]
|
278
|
+
def method_missing(m, *args)
|
279
|
+
if args.empty? && valid_property?(m.to_s)
|
280
|
+
get_property(m.to_s)
|
281
|
+
else
|
282
|
+
super
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# Returns a Hash representation of the object.
|
287
|
+
#
|
288
|
+
# @return [Hash{Symbol => Object}]
|
289
|
+
def to_h
|
290
|
+
h = {}
|
291
|
+
self.class.known_properties.each do |property|
|
292
|
+
val = __get_property(property)
|
293
|
+
h[property.to_sym] = val
|
294
|
+
end
|
295
|
+
|
296
|
+
get_infolist.first.each do |property, value|
|
297
|
+
prop = __get_property(property)
|
298
|
+
h[property] = prop
|
299
|
+
end
|
300
|
+
|
301
|
+
h
|
302
|
+
end
|
303
|
+
|
304
|
+
def self.alias_methods(type)
|
305
|
+
alias_method "#{type}_get_integer", :get_integer
|
306
|
+
alias_method "#{type}_get_string", :get_string
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
include ClassMethods
|
311
|
+
end
|
312
|
+
end
|