fig 1.1.0 → 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.
- data/Changes +94 -0
- data/lib/fig.rb +1 -1
- data/lib/fig/command.rb +18 -7
- data/lib/fig/command/action/dump_package_definition_for_command_line.rb +2 -2
- data/lib/fig/command/action/dump_package_definition_parsed.rb +2 -2
- data/lib/fig/command/action/dump_package_definition_text.rb +2 -2
- data/lib/fig/command/action/source_package.rb +65 -0
- data/lib/fig/command/options.rb +64 -15
- data/lib/fig/command/options/parser.rb +24 -14
- data/lib/fig/command/package_applier.rb +32 -7
- data/lib/fig/command/package_loader.rb +16 -7
- data/lib/fig/external_program.rb +72 -0
- data/lib/fig/figrc.rb +1 -1
- data/lib/fig/grammar/v0.rb +2 -2
- data/lib/fig/grammar/v0.treetop +2 -2
- data/lib/fig/grammar/v1.rb +17 -1737
- data/lib/fig/grammar/v1.treetop +6 -217
- data/lib/fig/grammar/v1_base.rb +1750 -0
- data/lib/fig/grammar/v1_base.treetop +229 -0
- data/lib/fig/grammar/v2.rb +508 -0
- data/lib/fig/grammar/v2.treetop +65 -0
- data/lib/fig/grammar_monkey_patches.rb +7 -0
- data/lib/fig/no_such_package_config_error.rb +3 -1
- data/lib/fig/not_yet_parsed_package.rb +27 -0
- data/lib/fig/operating_system.rb +5 -5
- data/lib/fig/package.rb +20 -2
- data/lib/fig/package_definition_text_assembler.rb +2 -1
- data/lib/fig/package_descriptor.rb +11 -4
- data/lib/fig/parser.rb +44 -58
- data/lib/fig/parser_package_build_state.rb +39 -4
- data/lib/fig/protocol/file.rb +2 -2
- data/lib/fig/protocol/ftp.rb +15 -10
- data/lib/fig/protocol/http.rb +1 -1
- data/lib/fig/protocol/netrc_enabled.rb +29 -16
- data/lib/fig/protocol/sftp.rb +19 -12
- data/lib/fig/repository.rb +33 -21
- data/lib/fig/repository_package_publisher.rb +129 -8
- data/lib/fig/runtime_environment.rb +114 -28
- data/lib/fig/statement/include.rb +21 -4
- data/lib/fig/statement/include_file.rb +94 -0
- data/lib/fig/unparser.rb +15 -7
- data/lib/fig/unparser/v1.rb +2 -80
- data/lib/fig/unparser/v1_base.rb +85 -0
- data/lib/fig/unparser/v2.rb +55 -0
- data/lib/fig/working_directory_maintainer.rb +12 -0
- metadata +61 -51
@@ -0,0 +1,65 @@
|
|
1
|
+
# Treetop (http://treetop.rubyforge.org/) grammar for package definitions in v2
|
2
|
+
# format.
|
3
|
+
|
4
|
+
require 'treetop'
|
5
|
+
|
6
|
+
require 'fig/grammar/base'
|
7
|
+
require 'fig/grammar/v1_base'
|
8
|
+
require 'fig/grammar/version'
|
9
|
+
|
10
|
+
module Fig
|
11
|
+
module Grammar
|
12
|
+
grammar V2
|
13
|
+
include Fig::Grammar::Base
|
14
|
+
include Fig::Grammar::Version
|
15
|
+
include Fig::Grammar::V1Base
|
16
|
+
|
17
|
+
# It would nice to be able to put this into Fig::Grammar::V1Base, but it
|
18
|
+
# looks like the root has to be declared in the concrete grammar.
|
19
|
+
rule package
|
20
|
+
optional_ws_or_comment
|
21
|
+
grammar_version:grammar_version?
|
22
|
+
statements:(package_statement_with_ws*)
|
23
|
+
optional_ws_or_comment
|
24
|
+
{
|
25
|
+
def to_package(unparsed_package, build_state)
|
26
|
+
return build_state.new_package_statement(
|
27
|
+
unparsed_package, grammar_version, statements
|
28
|
+
)
|
29
|
+
end
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
rule config_statement
|
34
|
+
override / include / include_file / command / path / set
|
35
|
+
end
|
36
|
+
|
37
|
+
rule include_file
|
38
|
+
statement_start:'include-file'
|
39
|
+
ws_or_comment+
|
40
|
+
path:file_path
|
41
|
+
config:(':' config_name:config_name)?
|
42
|
+
{
|
43
|
+
def to_config_statement(build_state)
|
44
|
+
config_name = nil
|
45
|
+
if config.respond_to? :config_name
|
46
|
+
config_name = config.config_name
|
47
|
+
end
|
48
|
+
|
49
|
+
return build_state.new_include_file_statement(
|
50
|
+
statement_start, path, config_name
|
51
|
+
)
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
rule file_path
|
57
|
+
# This is like quoted_or_bare_string, but disallows unquoted colons so
|
58
|
+
# that we can differentiate config names.
|
59
|
+
'"' ( [^"\\] / '\\' . )* '"' /
|
60
|
+
"'" ( [^'\\] / '\\' . )* "'" /
|
61
|
+
[^\s#:]+
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -4,6 +4,7 @@
|
|
4
4
|
|
5
5
|
require 'fig/grammar/v0'
|
6
6
|
require 'fig/grammar/v1'
|
7
|
+
require 'fig/grammar/v2'
|
7
8
|
|
8
9
|
module Fig; end
|
9
10
|
module Fig::Grammar; end
|
@@ -19,3 +20,9 @@ class Fig::Grammar::V1Parser
|
|
19
20
|
return 1
|
20
21
|
end
|
21
22
|
end
|
23
|
+
|
24
|
+
class Fig::Grammar::V2Parser
|
25
|
+
def version()
|
26
|
+
return 2
|
27
|
+
end
|
28
|
+
end
|
@@ -5,11 +5,13 @@ module Fig
|
|
5
5
|
# User specified a configuration for a Package that does not exist.
|
6
6
|
class NoSuchPackageConfigError < UserInputError
|
7
7
|
attr_reader :descriptor
|
8
|
+
attr_reader :package
|
8
9
|
|
9
|
-
def initialize(message, descriptor)
|
10
|
+
def initialize(message, descriptor, package)
|
10
11
|
super(message)
|
11
12
|
|
12
13
|
@descriptor = descriptor
|
14
|
+
@package = package
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Fig; end
|
2
|
+
|
3
|
+
# Metadata about a package definition file that hasn't been read yet.
|
4
|
+
class Fig::NotYetParsedPackage
|
5
|
+
attr_accessor :descriptor
|
6
|
+
attr_accessor :working_directory
|
7
|
+
attr_accessor :base_directory
|
8
|
+
attr_accessor :source_description
|
9
|
+
attr_accessor :unparsed_text
|
10
|
+
|
11
|
+
def extended_source_description()
|
12
|
+
if source_description
|
13
|
+
if source_description.start_with? working_directory
|
14
|
+
return source_description
|
15
|
+
end
|
16
|
+
|
17
|
+
extended = source_description
|
18
|
+
if working_directory != '.'
|
19
|
+
extended << " (#{working_directory})"
|
20
|
+
end
|
21
|
+
|
22
|
+
return extended
|
23
|
+
end
|
24
|
+
|
25
|
+
return working_directory
|
26
|
+
end
|
27
|
+
end
|
data/lib/fig/operating_system.rb
CHANGED
@@ -94,21 +94,21 @@ class Fig::OperatingSystem
|
|
94
94
|
|
95
95
|
# Determine whether we need to update something. Returns nil to indicate
|
96
96
|
# "don't know".
|
97
|
-
def path_up_to_date?(url, path)
|
97
|
+
def path_up_to_date?(url, path, prompt_for_login)
|
98
98
|
return false if ! File.exist? path
|
99
99
|
|
100
100
|
protocol, uri = decode_protocol url
|
101
|
-
return protocol.path_up_to_date? uri, path
|
101
|
+
return protocol.path_up_to_date? uri, path, prompt_for_login
|
102
102
|
end
|
103
103
|
|
104
104
|
# Returns whether the file was not downloaded because the file already
|
105
105
|
# exists and is already up-to-date.
|
106
|
-
def download(url, path)
|
106
|
+
def download(url, path, prompt_for_login)
|
107
107
|
protocol, uri = decode_protocol url
|
108
108
|
|
109
109
|
FileUtils.mkdir_p(File.dirname path)
|
110
110
|
|
111
|
-
return protocol.download uri, path
|
111
|
+
return protocol.download uri, path, prompt_for_login
|
112
112
|
end
|
113
113
|
|
114
114
|
# Returns the basename and full path to the download.
|
@@ -118,7 +118,7 @@ class Fig::OperatingSystem
|
|
118
118
|
basename = CGI.unescape Fig::URL.parse(url).path.split('/').last
|
119
119
|
path = File.join(download_directory, basename)
|
120
120
|
|
121
|
-
download(url, path)
|
121
|
+
download(url, path, false)
|
122
122
|
|
123
123
|
return basename, path
|
124
124
|
end
|
data/lib/fig/package.rb
CHANGED
@@ -22,20 +22,38 @@ class Fig::Package
|
|
22
22
|
attr_reader :version
|
23
23
|
attr_reader :description
|
24
24
|
attr_reader :runtime_directory
|
25
|
+
attr_reader :base_directory
|
25
26
|
attr_reader :statements
|
26
27
|
attr_accessor :backtrace
|
27
28
|
attr_accessor :unparsed_text
|
28
29
|
|
29
|
-
def initialize(
|
30
|
+
def initialize(
|
31
|
+
name,
|
32
|
+
version,
|
33
|
+
description,
|
34
|
+
runtime_directory,
|
35
|
+
base_directory,
|
36
|
+
statements,
|
37
|
+
synthetic
|
38
|
+
)
|
30
39
|
@name = name
|
31
40
|
@version = version
|
32
41
|
@description = description
|
33
42
|
@runtime_directory = runtime_directory
|
43
|
+
@base_directory = base_directory
|
34
44
|
@statements = statements
|
45
|
+
@synthetic = synthetic
|
35
46
|
@applied_config_names = []
|
36
47
|
@backtrace = nil
|
37
48
|
end
|
38
49
|
|
50
|
+
# Was this package (supposedly) created from something other than usual
|
51
|
+
# parsing? (Note that some tests artificially create "non-synthetic"
|
52
|
+
# instances.)
|
53
|
+
def synthetic?
|
54
|
+
return @synthetic
|
55
|
+
end
|
56
|
+
|
39
57
|
# Is this the base package?
|
40
58
|
def base?()
|
41
59
|
return @base
|
@@ -64,7 +82,7 @@ class Fig::Package
|
|
64
82
|
|
65
83
|
message = %Q<There is no "#{config_description}" config.>
|
66
84
|
|
67
|
-
raise Fig::NoSuchPackageConfigError.new(message, descriptor)
|
85
|
+
raise Fig::NoSuchPackageConfigError.new(message, descriptor, self)
|
68
86
|
end
|
69
87
|
|
70
88
|
def <=>(other)
|
@@ -2,6 +2,7 @@ require 'fig/statement/grammar_version'
|
|
2
2
|
require 'fig/unparser'
|
3
3
|
require 'fig/unparser/v0'
|
4
4
|
require 'fig/unparser/v1'
|
5
|
+
require 'fig/unparser/v2'
|
5
6
|
|
6
7
|
module Fig; end
|
7
8
|
|
@@ -34,7 +35,7 @@ class Fig::PackageDefinitionTextAssembler
|
|
34
35
|
def add_output(*statements)
|
35
36
|
# Version gets determined by other statements, not by existing grammar.
|
36
37
|
@output_statements <<
|
37
|
-
statements.reject { |s| s.is_a? Fig::Statement::GrammarVersion }
|
38
|
+
statements.flatten.reject { |s| s.is_a? Fig::Statement::GrammarVersion }
|
38
39
|
|
39
40
|
@output_statements.flatten!
|
40
41
|
|
@@ -6,7 +6,8 @@ module Fig; end
|
|
6
6
|
class Fig::PackageDescriptor
|
7
7
|
include Comparable
|
8
8
|
|
9
|
-
|
9
|
+
UNBRACKETED_COMPONENT_PATTERN = / (?! [.]{1,2} $) [a-zA-Z0-9_.-]+ /x
|
10
|
+
COMPONENT_PATTERN = / \A #{UNBRACKETED_COMPONENT_PATTERN} \z /x
|
10
11
|
|
11
12
|
attr_reader :name, :version, :config, :original_string, :description
|
12
13
|
|
@@ -65,9 +66,9 @@ class Fig::PackageDescriptor
|
|
65
66
|
# most likely the result of invoking
|
66
67
|
# Fig::Statement.position_description().
|
67
68
|
def initialize(name, version, config, options = {})
|
68
|
-
@name = name
|
69
|
-
@version = version
|
70
|
-
@config = config
|
69
|
+
@name = translate_component(name)
|
70
|
+
@version = translate_component(version)
|
71
|
+
@config = translate_component(config)
|
71
72
|
@original_string = options[:original_string]
|
72
73
|
@description = options[:description]
|
73
74
|
|
@@ -94,6 +95,12 @@ class Fig::PackageDescriptor
|
|
94
95
|
|
95
96
|
private
|
96
97
|
|
98
|
+
def translate_component(value)
|
99
|
+
return if value.nil?
|
100
|
+
return if value.empty?
|
101
|
+
return value
|
102
|
+
end
|
103
|
+
|
97
104
|
def validate_component(
|
98
105
|
value, name, presence_requirement_symbol, options
|
99
106
|
)
|
data/lib/fig/parser.rb
CHANGED
@@ -3,6 +3,7 @@ require 'set'
|
|
3
3
|
require 'fig/grammar/version_identification'
|
4
4
|
require 'fig/grammar/v0'
|
5
5
|
require 'fig/grammar/v1'
|
6
|
+
require 'fig/grammar/v2'
|
6
7
|
require 'fig/grammar_monkey_patches'
|
7
8
|
require 'fig/logging'
|
8
9
|
require 'fig/package_parse_error'
|
@@ -22,123 +23,108 @@ class Fig::Parser
|
|
22
23
|
@check_include_versions = check_include_versions
|
23
24
|
end
|
24
25
|
|
25
|
-
def parse_package(
|
26
|
-
version = get_grammar_version
|
27
|
-
descriptor, directory, source_description, unparsed_text
|
28
|
-
)
|
26
|
+
def parse_package(unparsed_package)
|
27
|
+
version = get_grammar_version unparsed_package
|
29
28
|
|
30
29
|
if version == 0
|
31
|
-
return parse_v0
|
30
|
+
return parse_v0 unparsed_package
|
32
31
|
end
|
33
32
|
|
34
|
-
return
|
33
|
+
return parse_v1_or_later version, unparsed_package
|
35
34
|
end
|
36
35
|
|
37
36
|
private
|
38
37
|
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
PARSER_CLASS = {
|
39
|
+
1 => Fig::Grammar::V1Parser,
|
40
|
+
2 => Fig::Grammar::V2Parser,
|
41
|
+
}
|
42
|
+
|
43
|
+
# TODO: Remove this once stablized.
|
44
|
+
@@seen_v2 = false
|
45
|
+
|
46
|
+
def get_grammar_version(unparsed_package)
|
42
47
|
version_parser = Fig::Grammar::VersionIdentificationParser.new()
|
43
48
|
|
44
|
-
extended_description =
|
45
|
-
extend_source_description(directory, source_description)
|
49
|
+
extended_description = unparsed_package.extended_source_description
|
46
50
|
|
47
|
-
result = version_parser.parse(unparsed_text)
|
51
|
+
result = version_parser.parse(unparsed_package.unparsed_text)
|
48
52
|
if result.nil?
|
49
|
-
raise_parse_error(
|
53
|
+
raise_parse_error(
|
54
|
+
version_parser,
|
55
|
+
unparsed_package.unparsed_text,
|
56
|
+
extended_description
|
57
|
+
)
|
50
58
|
end
|
51
59
|
|
52
60
|
statement = result.get_grammar_version(
|
53
|
-
Fig::ParserPackageBuildState.new(
|
61
|
+
Fig::ParserPackageBuildState.new(
|
62
|
+
nil, unparsed_package.descriptor, extended_description
|
63
|
+
)
|
54
64
|
)
|
55
65
|
return 0 if not statement
|
56
66
|
|
57
67
|
version = statement.version
|
58
|
-
if version >
|
68
|
+
if version > 2
|
59
69
|
raise Fig::PackageParseError.new(
|
60
70
|
%Q<Don't know how to parse grammar version #{version}#{statement.position_string()}.>
|
61
71
|
)
|
62
72
|
end
|
73
|
+
if version == 2 && ! @@seen_v2
|
74
|
+
@@seen_v2 = true
|
75
|
+
Fig::Logging.info(
|
76
|
+
'Encountered v2 grammar. This is experimental and subject to change without notice.'
|
77
|
+
)
|
78
|
+
end
|
63
79
|
|
64
80
|
return version
|
65
81
|
end
|
66
82
|
|
67
|
-
def parse_v0(
|
68
|
-
stripped_text = unparsed_text.gsub(/#.*$/, '') # Blech.
|
83
|
+
def parse_v0(unparsed_package)
|
84
|
+
stripped_text = unparsed_package.unparsed_text.gsub(/#.*$/, '') # Blech.
|
69
85
|
|
70
86
|
v0_parser = Fig::Grammar::V0Parser.new
|
71
87
|
|
72
|
-
return drive_treetop_parser(
|
73
|
-
v0_parser,
|
74
|
-
descriptor,
|
75
|
-
directory,
|
76
|
-
source_description,
|
77
|
-
unparsed_text,
|
78
|
-
stripped_text
|
79
|
-
)
|
88
|
+
return drive_treetop_parser(v0_parser, unparsed_package, stripped_text)
|
80
89
|
end
|
81
90
|
|
82
|
-
def
|
83
|
-
|
91
|
+
def parse_v1_or_later(version, unparsed_package)
|
92
|
+
parser = PARSER_CLASS[version].new
|
84
93
|
|
85
94
|
return drive_treetop_parser(
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
source_description,
|
90
|
-
unparsed_text,
|
91
|
-
unparsed_text
|
95
|
+
parser,
|
96
|
+
unparsed_package,
|
97
|
+
unparsed_package.unparsed_text
|
92
98
|
)
|
93
99
|
end
|
94
100
|
|
95
101
|
def drive_treetop_parser(
|
96
|
-
parser,
|
97
|
-
descriptor,
|
98
|
-
directory,
|
99
|
-
source_description,
|
100
|
-
unparsed_text, # Ugh. V0 strips comments via regex.
|
101
|
-
cleaned_text
|
102
|
+
parser, unparsed_package, cleaned_text # Ugh. V0 strips comments via regex.
|
102
103
|
)
|
103
104
|
# Extra space at the end because most of the rules in the grammar require
|
104
105
|
# trailing whitespace.
|
105
106
|
result = parser.parse(cleaned_text + ' ')
|
106
107
|
|
107
|
-
extended_description =
|
108
|
-
extend_source_description(directory, source_description)
|
108
|
+
extended_description = unparsed_package.extended_source_description
|
109
109
|
|
110
110
|
if result.nil?
|
111
111
|
raise_parse_error(parser, cleaned_text, extended_description)
|
112
112
|
end
|
113
113
|
|
114
114
|
package = result.to_package(
|
115
|
-
|
115
|
+
unparsed_package,
|
116
116
|
Fig::ParserPackageBuildState.new(
|
117
|
-
parser.version, descriptor, extended_description
|
117
|
+
parser.version, unparsed_package.descriptor, extended_description
|
118
118
|
)
|
119
119
|
)
|
120
|
-
package.unparsed_text = unparsed_text
|
121
120
|
|
122
|
-
check_for_bad_urls(package, descriptor)
|
121
|
+
check_for_bad_urls(package, unparsed_package.descriptor)
|
123
122
|
check_for_multiple_command_statements(package)
|
124
123
|
check_for_missing_versions_on_include_statements(package)
|
125
124
|
|
126
125
|
return package
|
127
126
|
end
|
128
127
|
|
129
|
-
def extend_source_description(directory, original_description)
|
130
|
-
if original_description
|
131
|
-
extended = original_description
|
132
|
-
if directory != '.'
|
133
|
-
extended << " (#{directory})"
|
134
|
-
end
|
135
|
-
|
136
|
-
return extended
|
137
|
-
end
|
138
|
-
|
139
|
-
return directory
|
140
|
-
end
|
141
|
-
|
142
128
|
def raise_parse_error(treetop_parser, text, extended_description)
|
143
129
|
message = extended_description
|
144
130
|
|
@@ -6,6 +6,7 @@ require 'fig/statement/command'
|
|
6
6
|
require 'fig/statement/configuration'
|
7
7
|
require 'fig/statement/grammar_version'
|
8
8
|
require 'fig/statement/include'
|
9
|
+
require 'fig/statement/include_file'
|
9
10
|
require 'fig/statement/override'
|
10
11
|
require 'fig/statement/path'
|
11
12
|
require 'fig/statement/resource'
|
@@ -43,7 +44,7 @@ class Fig::ParserPackageBuildState
|
|
43
44
|
)
|
44
45
|
end
|
45
46
|
|
46
|
-
def new_package_statement(
|
47
|
+
def new_package_statement(unparsed_package, grammar_node, statement_nodes)
|
47
48
|
grammar_statement = nil
|
48
49
|
if grammar_node && ! grammar_node.empty?
|
49
50
|
grammar_statement = grammar_node.to_package_statement(self)
|
@@ -62,13 +63,18 @@ class Fig::ParserPackageBuildState
|
|
62
63
|
statement_objects << node.to_package_statement(self)
|
63
64
|
end
|
64
65
|
|
65
|
-
|
66
|
+
package = Fig::Package.new(
|
66
67
|
@descriptor.name,
|
67
68
|
@descriptor.version,
|
68
69
|
@descriptor.description,
|
69
|
-
|
70
|
-
|
70
|
+
unparsed_package.working_directory,
|
71
|
+
unparsed_package.base_directory,
|
72
|
+
statement_objects,
|
73
|
+
false,
|
71
74
|
)
|
75
|
+
package.unparsed_text = unparsed_package.unparsed_text
|
76
|
+
|
77
|
+
return package
|
72
78
|
end
|
73
79
|
|
74
80
|
def new_grammar_version_statement(keyword_node, version_node)
|
@@ -143,10 +149,39 @@ class Fig::ParserPackageBuildState
|
|
143
149
|
node_location(keyword_node),
|
144
150
|
@source_description,
|
145
151
|
include_descriptor,
|
152
|
+
nil,
|
146
153
|
@descriptor
|
147
154
|
)
|
148
155
|
end
|
149
156
|
|
157
|
+
def new_include_file_statement(keyword_node, path_node, config_name_node)
|
158
|
+
path, config_name =
|
159
|
+
Fig::Statement::IncludeFile.validate_and_process_raw_path_and_config_name(
|
160
|
+
path_node.text_value,
|
161
|
+
config_name_node.nil? ? nil : config_name_node.text_value,
|
162
|
+
) do
|
163
|
+
|description|
|
164
|
+
|
165
|
+
value_text = path_node.text_value
|
166
|
+
if ! config_name_node.nil?
|
167
|
+
value_text << ':'
|
168
|
+
value_text << config_name_node.text_value
|
169
|
+
end
|
170
|
+
|
171
|
+
raise Fig::PackageParseError.new(
|
172
|
+
%Q<Invalid include-file statement: "#{value_text}" #{description}#{node_location_description(path_node)}>
|
173
|
+
)
|
174
|
+
end
|
175
|
+
|
176
|
+
return Fig::Statement::IncludeFile.new(
|
177
|
+
node_location(keyword_node),
|
178
|
+
@source_description,
|
179
|
+
path,
|
180
|
+
config_name,
|
181
|
+
@descriptor,
|
182
|
+
)
|
183
|
+
end
|
184
|
+
|
150
185
|
def new_override_statement(keyword_node, descriptor_node)
|
151
186
|
override_descriptor =
|
152
187
|
Fig::Statement::Override.parse_descriptor(
|