locport 1.1.3 → 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.
- checksums.yaml +4 -4
- data/lib/locport/indexer.rb +178 -0
- data/lib/locport.rb +51 -148
- metadata +59 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3cbc4ad1881fb933fd3582c13cd2ed6c4d881b71d19430451c0ccb4f882c299a
|
|
4
|
+
data.tar.gz: abe62ddb5fa288edbb3376c0137558a90d84ce93906e638697399d3ac5077ad8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 568880abea66ec3c00d68ac0a3c5c02300fa348d8a2ba729c0a2cfbe805a2d23e0ccef8d88c71532634e44e03a2c4be97cb9b4fe21d257c81389bda2a67adecc
|
|
7
|
+
data.tar.gz: 8fe1b19ea6f27c437e54302b7b4c06f6aeadfaecfa204f81cd9fb66ec7c8b7db5cc9f2f9c9c0d1cb0a9f6e281d60ca43e8d223aadf1ed585a7568ad55170b41f
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "find"
|
|
4
|
+
require "socket"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "pathname"
|
|
7
|
+
|
|
8
|
+
module Locport
|
|
9
|
+
Address = Struct.new(:host, :port, :path, :line_number, :host_conflicts, :port_conflicts)
|
|
10
|
+
|
|
11
|
+
class Indexer
|
|
12
|
+
APP_NAME = "locport"
|
|
13
|
+
DOTFILE = ".localhost"
|
|
14
|
+
DATA_FILE = "projects"
|
|
15
|
+
PORT_RANGE = (30_000..60_000)
|
|
16
|
+
|
|
17
|
+
attr_reader :dotfiles, :projects, :addresses
|
|
18
|
+
|
|
19
|
+
def initialize(home_path: Dir.home, storage_base_dir: default_storage_base_dir)
|
|
20
|
+
@home_path = home_path
|
|
21
|
+
@storage_base_dir = storage_base_dir
|
|
22
|
+
@dotfiles = load_dotfiles
|
|
23
|
+
@projects = load_projects
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def index(path, recursive: false, shell: nil)
|
|
27
|
+
start_path = File.expand_path path.to_s
|
|
28
|
+
|
|
29
|
+
[].tap do |result|
|
|
30
|
+
begin
|
|
31
|
+
Find.find(start_path) do |path|
|
|
32
|
+
Find.prune if !recursive && File.directory?(path) && path.size > start_path.size
|
|
33
|
+
|
|
34
|
+
if File.basename(path) == DOTFILE && File.file?(path)
|
|
35
|
+
result << Pathname.new(path)
|
|
36
|
+
|
|
37
|
+
shell&.indent do
|
|
38
|
+
shell&.say "#{path} "
|
|
39
|
+
end
|
|
40
|
+
shell&.say "added", :green
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
rescue Errno::ENOENT
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
@dotfiles = (@dotfiles + result).uniq.sort
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load_dotfiles
|
|
51
|
+
File.read(storage_path).lines.map(&:strip).reject(&:empty?).map { |path| Pathname.new(path).join(DOTFILE) }
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
[]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def load_projects
|
|
57
|
+
@projects = {}.tap do |result|
|
|
58
|
+
@addresses = []
|
|
59
|
+
|
|
60
|
+
@dotfiles.each do |path|
|
|
61
|
+
File.read(path).each_line.with_index do |line, index|
|
|
62
|
+
dir = File.dirname path.to_s
|
|
63
|
+
|
|
64
|
+
key, source = cannonize_project_dir dir
|
|
65
|
+
result[key] ||= []
|
|
66
|
+
|
|
67
|
+
if line.strip =~ /^(.+):(\d+)$/
|
|
68
|
+
address = Address.new($1, $2.to_i, source, index + 1)
|
|
69
|
+
result[key] << address
|
|
70
|
+
@addresses << address
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
rescue Errno::ENOENT
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
reveal_address_conflicts
|
|
77
|
+
end.sort.to_h
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def port_listening?(port)
|
|
81
|
+
Socket.tcp("127.0.0.1", port, connect_timeout: 0.01) {} # 10ms timeout
|
|
82
|
+
true
|
|
83
|
+
rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, SocketError
|
|
84
|
+
false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def save
|
|
88
|
+
FileUtils.mkdir_p storage_dir
|
|
89
|
+
File.write storage_path, @dotfiles.map { |path| File.dirname(path) }.join("\n") + "\n"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def default_storage_base_dir
|
|
93
|
+
if Gem.win_platform?
|
|
94
|
+
ENV["APPDATA"] || File.join(Dir.home, "AppData", "Roaming")
|
|
95
|
+
else
|
|
96
|
+
ENV["XDG_DATA_HOME"] || File.join(Dir.home, ".local", "share")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def create_address(value, dir: Dir.pwd)
|
|
101
|
+
_, source = cannonize_project_dir dir
|
|
102
|
+
|
|
103
|
+
address = if value.strip =~ /^(.+):(\d+)$/
|
|
104
|
+
Address.new($1, $2.to_i, source)
|
|
105
|
+
else
|
|
106
|
+
Address.new(value, find_unused_port, source)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
@addresses << address
|
|
110
|
+
reveal_address_conflicts
|
|
111
|
+
|
|
112
|
+
address
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def append_address_to_dotfile(address, dir: Dir.pwd)
|
|
116
|
+
_, _, fullpath = cannonize_project_dir dir
|
|
117
|
+
|
|
118
|
+
File.open(fullpath, "a") do |file|
|
|
119
|
+
file.puts("#{address.host}:#{address.port}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
index dir
|
|
123
|
+
save
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def storage_path
|
|
127
|
+
File.join storage_dir, DATA_FILE
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
def storage_dir
|
|
132
|
+
File.join @storage_base_dir, APP_NAME
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def cannonize_project_dir(dir)
|
|
136
|
+
key = if dir.start_with?(@home_path.to_s)
|
|
137
|
+
dir.sub(@home_path.to_s, "~")
|
|
138
|
+
else
|
|
139
|
+
dir
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
source = "#{key}/#{DOTFILE}"
|
|
143
|
+
fullpath = "#{dir}/#{DOTFILE}"
|
|
144
|
+
|
|
145
|
+
[ key, source, fullpath ]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def reveal_address_conflicts
|
|
149
|
+
@addresses.each do |address|
|
|
150
|
+
address.host_conflicts = nil
|
|
151
|
+
address.port_conflicts = nil
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
@addresses.each do |address|
|
|
155
|
+
@addresses.each do |other_address|
|
|
156
|
+
next if address == other_address
|
|
157
|
+
|
|
158
|
+
if address.host == other_address.host
|
|
159
|
+
address.host_conflicts ||= []
|
|
160
|
+
address.host_conflicts << other_address
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if address.port == other_address.port
|
|
164
|
+
address.port_conflicts ||= []
|
|
165
|
+
address.port_conflicts << other_address
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def find_unused_port
|
|
172
|
+
loop do
|
|
173
|
+
port = rand PORT_RANGE
|
|
174
|
+
return port unless @addresses.any? { |address| address.port == port } || port_listening?(port)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
data/lib/locport.rb
CHANGED
|
@@ -1,205 +1,108 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "thor"
|
|
4
|
-
require "
|
|
5
|
-
require "pathname"
|
|
6
|
-
|
|
7
|
-
APP_NAME = "locport"
|
|
8
|
-
DATA_FILE = "projects"
|
|
9
|
-
DOTFILE = ".localhost"
|
|
10
|
-
PORT_RANGE = (30_000..60_000)
|
|
4
|
+
require "locport/indexer"
|
|
11
5
|
|
|
12
6
|
module Locport
|
|
13
7
|
class Main < Thor
|
|
8
|
+
COLOR_FAINT = "\e[2m"
|
|
9
|
+
|
|
14
10
|
default_task :list
|
|
15
11
|
|
|
16
12
|
def self.exit_on_failure?
|
|
17
13
|
true
|
|
18
14
|
end
|
|
19
15
|
|
|
20
|
-
desc "index [PATH]", "
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
say_error "You can create it from within that directory with `locport add`"
|
|
28
|
-
exit 1
|
|
16
|
+
desc "index [PATH]", "Add project path(s) to locport"
|
|
17
|
+
method_option :recursive, type: :boolean, default: false, aliases: "-r"
|
|
18
|
+
def index(*paths)
|
|
19
|
+
paths.each do |path|
|
|
20
|
+
say "Indexing "
|
|
21
|
+
say path, :blue
|
|
22
|
+
indexer.index(path, recursive: options.recursive, shell:)
|
|
29
23
|
end
|
|
30
24
|
|
|
31
|
-
|
|
25
|
+
indexer.save
|
|
32
26
|
|
|
33
|
-
|
|
34
|
-
say "Indexing ", :green
|
|
35
|
-
say project_path
|
|
36
|
-
end
|
|
27
|
+
say "Done ✓", :green
|
|
37
28
|
end
|
|
38
29
|
|
|
39
30
|
desc "add [HOST[:PORT]]", "Add a new host to .localhost file. \
|
|
40
31
|
Without arguments current directory name will be used and random port number assigned."
|
|
41
32
|
def add(host = "#{File.basename(Dir.pwd)}.localhost")
|
|
42
|
-
|
|
43
|
-
host = host_with_port.split(":").first
|
|
44
|
-
|
|
45
|
-
if used_ports.include?(port)
|
|
46
|
-
say_error "Port #{port} is "
|
|
47
|
-
say_error "already used. ", :red
|
|
48
|
-
say_error "See `locport list`"
|
|
49
|
-
exit 1
|
|
50
|
-
end
|
|
33
|
+
address = indexer.create_address host
|
|
51
34
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
35
|
+
say_address_conflicts address
|
|
36
|
+
|
|
37
|
+
if @conflicts_found
|
|
38
|
+
say "Can't add due to conflicts"
|
|
56
39
|
exit 1
|
|
57
40
|
end
|
|
58
41
|
|
|
59
|
-
|
|
60
|
-
index silent: true
|
|
42
|
+
indexer.append_address_to_dotfile address
|
|
61
43
|
|
|
62
|
-
say "#{
|
|
63
|
-
say "added ", :green
|
|
64
|
-
say "to #{DOTFILE}"
|
|
44
|
+
say "#{address.host}:#{address.port} ", :bold
|
|
45
|
+
say "added ✓", :green
|
|
65
46
|
end
|
|
66
47
|
|
|
67
48
|
desc "list", "List indexed projects, hosts and ports"
|
|
68
49
|
def list
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
used_hosts = []
|
|
72
|
-
conflicts_found = false
|
|
50
|
+
indexer.projects.each do |dir, addresses|
|
|
51
|
+
say dir, :blue
|
|
73
52
|
|
|
74
|
-
|
|
75
|
-
|
|
53
|
+
shell.indent do
|
|
54
|
+
addresses.each do |address|
|
|
55
|
+
port_color = indexer.port_listening?(address.port) ? :green : COLOR_FAINT
|
|
56
|
+
say "• ", port_color
|
|
76
57
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
else
|
|
81
|
-
used_ports << port
|
|
82
|
-
end
|
|
58
|
+
shell.indent(-1) do
|
|
59
|
+
say [ display_host(address.host), address.port ].join(":")
|
|
60
|
+
end
|
|
83
61
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
table_data << [ "", "╰ Host used before" ]
|
|
87
|
-
else
|
|
88
|
-
used_hosts << host
|
|
62
|
+
say_address_conflicts address
|
|
63
|
+
end
|
|
89
64
|
end
|
|
90
65
|
end
|
|
91
66
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if conflicts_found
|
|
95
|
-
say "Conflicts found!", :red
|
|
67
|
+
if @conflicts_found
|
|
68
|
+
say "Conflicts found", :red
|
|
96
69
|
exit 1
|
|
97
|
-
else
|
|
98
|
-
say "All hosts and ports are unique ✓", :green
|
|
99
70
|
end
|
|
100
71
|
end
|
|
101
72
|
|
|
102
73
|
desc "info", "Display tool information"
|
|
103
74
|
def info
|
|
104
|
-
say "
|
|
75
|
+
say "Index file: #{indexer.storage_path}"
|
|
105
76
|
end
|
|
106
77
|
|
|
107
78
|
private
|
|
108
|
-
def
|
|
109
|
-
|
|
110
|
-
@projects
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def used_ports
|
|
114
|
-
load_projects
|
|
115
|
-
@used_ports
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def used_hosts
|
|
119
|
-
load_projects
|
|
120
|
-
@used_hosts
|
|
79
|
+
def indexer
|
|
80
|
+
@indexer ||= Indexer.new
|
|
121
81
|
end
|
|
122
82
|
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@used_hosts = []
|
|
127
|
-
|
|
128
|
-
File.read(projects_file_path).lines.each do |project_path|
|
|
129
|
-
project_path = project_path.strip
|
|
130
|
-
dotfile_path = Pathname.new(project_path).join(DOTFILE)
|
|
131
|
-
next unless File.exist?(dotfile_path)
|
|
132
|
-
|
|
133
|
-
hosts = File.read(dotfile_path).lines
|
|
134
|
-
|
|
135
|
-
hosts.each do |host_with_port|
|
|
136
|
-
host, port = host_with_port.strip.split(":")
|
|
137
|
-
port = port.to_i
|
|
138
|
-
@projects << [ project_path, host, port ]
|
|
139
|
-
|
|
140
|
-
unless @used_ports.include?(port)
|
|
141
|
-
@used_ports << port
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
unless @used_hosts.include?(host)
|
|
145
|
-
@used_hosts << host
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
@projects
|
|
151
|
-
rescue Errno::ENOENT
|
|
152
|
-
@projects
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def ensure_port(host)
|
|
156
|
-
if host =~ /:([\d]+)$/
|
|
157
|
-
[ host, $1.to_i ]
|
|
158
|
-
else
|
|
159
|
-
port = find_unused_port
|
|
160
|
-
[ "#{host}:#{port}", port ]
|
|
161
|
-
end
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def find_unused_port
|
|
165
|
-
loop do
|
|
166
|
-
port = rand PORT_RANGE
|
|
167
|
-
return port unless used_ports.include?(port)
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def storage_base_dir
|
|
172
|
-
if Gem.win_platform?
|
|
173
|
-
ENV["APPDATA"] || File.join(Dir.home, "AppData", "Roaming")
|
|
83
|
+
def display_host(host)
|
|
84
|
+
if host.include?("://")
|
|
85
|
+
host
|
|
174
86
|
else
|
|
175
|
-
|
|
87
|
+
"http://#{host}"
|
|
176
88
|
end
|
|
177
89
|
end
|
|
178
90
|
|
|
179
|
-
def
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
def projects_file_path
|
|
184
|
-
FileUtils.mkdir_p(storage_dir)
|
|
185
|
-
File.join(storage_dir, DATA_FILE)
|
|
186
|
-
end
|
|
91
|
+
def say_address_conflicts(address)
|
|
92
|
+
if address.port_conflicts
|
|
93
|
+
@conflicts_found = true
|
|
187
94
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
95
|
+
address.port_conflicts.each do |conflicting_address|
|
|
96
|
+
say "╰ Port also at #{conflicting_address.path}:#{conflicting_address.line_number}", :red
|
|
97
|
+
end
|
|
191
98
|
end
|
|
192
99
|
|
|
193
|
-
|
|
100
|
+
if address.host_conflicts
|
|
101
|
+
@conflicts_found = true
|
|
194
102
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
def append_to_dotfile(line)
|
|
201
|
-
File.open(DOTFILE, "a") do |f|
|
|
202
|
-
f.puts(line)
|
|
103
|
+
address.host_conflicts.each do |conflicting_address|
|
|
104
|
+
say "╰ Host also at #{conflicting_address.path}:#{conflicting_address.line_number}", :red
|
|
105
|
+
end
|
|
203
106
|
end
|
|
204
107
|
end
|
|
205
108
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: locport
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Starsi
|
|
@@ -23,6 +23,62 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: testerobly
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: debug
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '1.10'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '1.10'
|
|
26
82
|
description: Overview of localhost ports used across projects. Prevent conflicts.
|
|
27
83
|
email: klevo@klevo.sk
|
|
28
84
|
executables:
|
|
@@ -33,6 +89,7 @@ files:
|
|
|
33
89
|
- LICENSE
|
|
34
90
|
- bin/locport
|
|
35
91
|
- lib/locport.rb
|
|
92
|
+
- lib/locport/indexer.rb
|
|
36
93
|
homepage: https://github.com/klevo/locport
|
|
37
94
|
licenses:
|
|
38
95
|
- MIT
|
|
@@ -44,7 +101,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
44
101
|
requirements:
|
|
45
102
|
- - ">="
|
|
46
103
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
104
|
+
version: '3.2'
|
|
48
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
106
|
requirements:
|
|
50
107
|
- - ">="
|