sevgi 0.95.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/CHANGELOG.md +221 -2
- data/README.md +27 -9
- data/bin/igsev +6 -0
- data/bin/sevgi +1 -1
- data/lib/sevgi/binaries/igsev.rb +161 -0
- data/lib/sevgi/binaries/rake.rb +8 -3
- data/lib/sevgi/binaries/sevgi.rb +74 -31
- data/lib/sevgi/executor/error.rb +24 -24
- data/lib/sevgi/executor/result.rb +54 -0
- data/lib/sevgi/executor/scope.rb +27 -9
- data/lib/sevgi/executor/source.rb +16 -7
- data/lib/sevgi/executor.rb +106 -106
- data/lib/sevgi/skill.rb +42 -0
- data/lib/sevgi/svg.rb +166 -0
- data/lib/sevgi/toplevel/derender.rb +95 -7
- data/lib/sevgi/toplevel/executor.rb +59 -15
- data/lib/sevgi/toplevel/graphics.rb +93 -6
- data/lib/sevgi/toplevel/sundries.rb +24 -5
- data/lib/sevgi/toplevel.rb +7 -8
- data/lib/sevgi/version.rb +1 -1
- data/lib/sevgi.rb +105 -14
- metadata +36 -17
data/lib/sevgi/binaries/sevgi.rb
CHANGED
|
@@ -1,30 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "sevgi"
|
|
4
|
+
require "sevgi/skill"
|
|
4
5
|
|
|
5
6
|
module Sevgi
|
|
6
7
|
# Command-line entrypoint implementations shipped with Sevgi.
|
|
8
|
+
# @api private
|
|
7
9
|
module Binaries
|
|
8
10
|
# Implements the `sevgi` executable.
|
|
11
|
+
# @api private
|
|
9
12
|
module Sevgi
|
|
10
13
|
extend self
|
|
11
14
|
|
|
12
15
|
# Executable name used in help output.
|
|
13
16
|
PROGNAME = "sevgi"
|
|
14
17
|
|
|
18
|
+
# Logical source name used for standard input.
|
|
19
|
+
STDIN_NAME = "output.sevgi"
|
|
20
|
+
|
|
15
21
|
# Error raised for invalid command-line usage.
|
|
16
22
|
Error = Class.new(::Sevgi::Error)
|
|
17
23
|
|
|
24
|
+
FLAGS = {
|
|
25
|
+
"-x" => :vomit,
|
|
26
|
+
"--exception" => :vomit,
|
|
27
|
+
"-h" => :help,
|
|
28
|
+
"--help" => :help,
|
|
29
|
+
"--skill" => :skill,
|
|
30
|
+
"-v" => :version,
|
|
31
|
+
"--version" => :version
|
|
32
|
+
}.freeze
|
|
33
|
+
|
|
18
34
|
# Parsed command-line options for the `sevgi` executable.
|
|
19
35
|
# @api private
|
|
20
|
-
Options = Struct.new(:require, :
|
|
36
|
+
Options = Struct.new(:require, :vomit, :help, :skill, :version, :as) do
|
|
21
37
|
# Parses command-line options and removes them from the argv array.
|
|
22
38
|
# @param argv [Array<String>] mutable command-line argument array
|
|
23
39
|
# @return [Sevgi::Binaries::Sevgi::Options] parsed options
|
|
24
|
-
# @raise [Sevgi::Binaries::Sevgi::Error] when an option is not recognized
|
|
40
|
+
# @raise [Sevgi::Binaries::Sevgi::Error] when an option is not recognized or a required value is missing
|
|
25
41
|
def self.parse(argv)
|
|
26
42
|
new.tap do |options|
|
|
27
|
-
argv.
|
|
43
|
+
until argv.empty? || argv.first == "-" || !argv.first.start_with?("-")
|
|
44
|
+
if argv.first == "--"
|
|
45
|
+
argv.shift
|
|
46
|
+
break
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
option(argv, options)
|
|
50
|
+
end
|
|
28
51
|
end
|
|
29
52
|
end
|
|
30
53
|
|
|
@@ -32,18 +55,14 @@ module Sevgi
|
|
|
32
55
|
private
|
|
33
56
|
|
|
34
57
|
def option(argv, options)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
options
|
|
38
|
-
|
|
39
|
-
options.
|
|
40
|
-
|
|
41
|
-
options.
|
|
42
|
-
|
|
43
|
-
when "-h", "--help"
|
|
44
|
-
options.help = true
|
|
45
|
-
when "-v", "--version"
|
|
46
|
-
options.version = true
|
|
58
|
+
arg = argv.shift
|
|
59
|
+
if (flag = FLAGS[arg])
|
|
60
|
+
options[flag] = true
|
|
61
|
+
elsif ["-r", "--require"].include?(arg)
|
|
62
|
+
options.require = argv.shift || Error.("Option requires a library: #{arg}")
|
|
63
|
+
elsif arg == "--as"
|
|
64
|
+
options.as = argv.shift
|
|
65
|
+
Error.("Option requires a name: --as") if options.as.nil? || options.as.empty?
|
|
47
66
|
else
|
|
48
67
|
Error.("Not a valid option: #{arg}")
|
|
49
68
|
end
|
|
@@ -51,64 +70,88 @@ module Sevgi
|
|
|
51
70
|
end
|
|
52
71
|
end
|
|
53
72
|
|
|
54
|
-
private_constant :Options
|
|
73
|
+
private_constant :FLAGS, :Options, :STDIN_NAME
|
|
55
74
|
|
|
56
75
|
# Runs the `sevgi` command-line interface.
|
|
57
76
|
# @param argv [Array<String>, String, nil] command-line arguments
|
|
58
77
|
# @return [nil]
|
|
59
78
|
# @raise [Sevgi::Executor::Error] when `--exception` or `SEVGI_VOMIT` requests raw executor errors
|
|
60
|
-
# @raise [SystemExit] when
|
|
79
|
+
# @raise [SystemExit] when argv does not match `[options...] [--] [file|-]` or script execution aborts
|
|
61
80
|
def call(argv)
|
|
62
81
|
return puts(help) if (options = Options.parse(argv = Array(argv))).help
|
|
82
|
+
return puts(Skill.path) if options.skill
|
|
63
83
|
return puts(::Sevgi::VERSION) if options.version
|
|
64
84
|
|
|
65
|
-
file = argv
|
|
66
|
-
handle(run(file, options),
|
|
85
|
+
file = operand(argv)
|
|
86
|
+
handle(run(file, options), options)
|
|
67
87
|
|
|
68
|
-
rescue
|
|
88
|
+
rescue Skill::Error => e
|
|
69
89
|
abort(e.message)
|
|
90
|
+
rescue Binaries::Sevgi::Error => e
|
|
91
|
+
abort("#{e.message}\n\n#{help}")
|
|
70
92
|
end
|
|
71
93
|
|
|
72
94
|
private
|
|
73
95
|
|
|
74
|
-
def die(error
|
|
75
|
-
|
|
76
|
-
warn("")
|
|
77
|
-
error.backtrace!.each { warn(" #{it}") }
|
|
78
|
-
|
|
96
|
+
def die(error)
|
|
97
|
+
message = error.message.empty? ? error.cause.class.to_s : error.message
|
|
98
|
+
warn(message, "", *error.load_backtrace.map { " #{it}" })
|
|
79
99
|
exit(1)
|
|
80
100
|
end
|
|
81
101
|
|
|
82
|
-
def handle(result,
|
|
102
|
+
def handle(result, options)
|
|
83
103
|
return unless result&.error?
|
|
84
104
|
|
|
85
105
|
raise result.error if options.vomit || ENV[ENVVOMIT]
|
|
86
106
|
|
|
87
|
-
die(result.error
|
|
107
|
+
die(result.error)
|
|
88
108
|
end
|
|
89
109
|
|
|
90
110
|
def help
|
|
91
111
|
<<~HELP
|
|
92
|
-
Usage: #{PROGNAME} [options...]
|
|
112
|
+
Usage: #{PROGNAME} [options...] [--] [Sevgi file|-]
|
|
93
113
|
|
|
94
114
|
See documentation for detailed help.
|
|
95
115
|
|
|
96
116
|
Options:
|
|
97
117
|
|
|
98
|
-
|
|
118
|
+
--as NAME Evaluate input as NAME for implicit output names
|
|
99
119
|
-r, --require LIB Require Ruby LIB
|
|
120
|
+
--skill Display the packaged agent skill path
|
|
100
121
|
-x, --exception Raise exception instead of abort
|
|
122
|
+
-- Stop option parsing
|
|
101
123
|
|
|
102
124
|
-h, --help Show this help
|
|
103
125
|
-v, --version Display version
|
|
104
126
|
HELP
|
|
105
127
|
end
|
|
106
128
|
|
|
129
|
+
def operand(argv)
|
|
130
|
+
file = argv.shift
|
|
131
|
+
Error.("Unexpected argument: #{argv.first}") unless argv.empty?
|
|
132
|
+
|
|
133
|
+
file unless file == "-"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def execute_file(file, options)
|
|
137
|
+
name = source_name(options.as) if options.as
|
|
138
|
+
::Sevgi.execute_file(file, as: name, require: options.require, main: true)
|
|
139
|
+
end
|
|
140
|
+
|
|
107
141
|
def run(file, options)
|
|
108
|
-
|
|
142
|
+
return execute_file(file, options) if file
|
|
109
143
|
|
|
110
|
-
::Sevgi.
|
|
144
|
+
::Sevgi.execute($stdin.read, file: source_name(options.as), require: options.require, main: true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def source_name(name)
|
|
148
|
+
return STDIN_NAME unless name
|
|
149
|
+
|
|
150
|
+
Error.("Option requires a name, not a path: --as") unless ::File.basename(name) == name
|
|
151
|
+
F.subext(".sevgi", name)
|
|
111
152
|
end
|
|
112
153
|
end
|
|
113
154
|
end
|
|
155
|
+
|
|
156
|
+
private_constant :Binaries
|
|
114
157
|
end
|
data/lib/sevgi/executor/error.rb
CHANGED
|
@@ -3,48 +3,48 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
class Executor
|
|
5
5
|
# Raised when a source attempts to load another source already active in the same scope.
|
|
6
|
+
# @see https://sevgi.roktas.dev/usage/#execute Execute source guide
|
|
6
7
|
class CycleError < ::Sevgi::Error
|
|
7
8
|
end
|
|
8
9
|
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# @return [Sevgi::Executor::Scope]
|
|
17
|
-
attr_reader :scope
|
|
10
|
+
# Raised when nested Sevgi loads exceed the executor's supported active-source depth.
|
|
11
|
+
#
|
|
12
|
+
# The limit is 128 active sources, including the entry source. It does not count Ruby stack frames
|
|
13
|
+
# or guarantee protection from stack exhaustion within scripts.
|
|
14
|
+
# @see https://sevgi.roktas.dev/usage/#execute Execute source guide
|
|
15
|
+
class LoadDepthError < ::Sevgi::Error
|
|
16
|
+
end
|
|
18
17
|
|
|
18
|
+
# Wraps an exception raised while executing Sevgi script source. Its visited source snapshot records every source in
|
|
19
|
+
# load order. It is not the active load stack at the instant of failure.
|
|
20
|
+
# @see https://sevgi.roktas.dev/usage/#execute Execute source guide
|
|
21
|
+
class Error < ::Sevgi::Error
|
|
19
22
|
# Builds an executor error wrapper.
|
|
20
23
|
# @param error [Exception] original exception
|
|
21
|
-
# @param
|
|
24
|
+
# @param stack [Array<String>] source file keys visited in load order. The Array and its String entries are copied
|
|
25
|
+
# and frozen
|
|
22
26
|
# @return [void]
|
|
23
|
-
def initialize(error,
|
|
24
|
-
@
|
|
25
|
-
@
|
|
27
|
+
def initialize(error, stack)
|
|
28
|
+
@cause = error
|
|
29
|
+
@stack = stack.map { it.dup.freeze }.freeze
|
|
26
30
|
|
|
27
31
|
super(error.message)
|
|
28
32
|
end
|
|
29
33
|
|
|
30
|
-
# Returns backtrace entries that belong to the Sevgi
|
|
31
|
-
# @return [Array<String>] filtered backtrace lines relative to the current directory
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
# Returns backtrace entries that belong to the visited Sevgi source set.
|
|
35
|
+
# @return [Array<String>] filtered backtrace lines relative to the current directory, or an empty Array when the
|
|
36
|
+
# original exception has no backtrace
|
|
37
|
+
def load_backtrace
|
|
38
|
+
sources = @stack.map { ::File.expand_path(it) }
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
.backtrace
|
|
40
|
+
Array(cause.backtrace)
|
|
37
41
|
.select { sources.include?(::File.expand_path(it.split(":", 2).first)) }
|
|
38
42
|
.map { |line| line.delete_prefix("#{::Dir.pwd}/") }
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
# Returns the original exception as the wrapped cause.
|
|
42
46
|
# @return [Exception]
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# Returns the script load stack active at failure time.
|
|
46
|
-
# @return [Array<String>] script file names in load order
|
|
47
|
-
def stack = scope.stack
|
|
47
|
+
attr_reader :cause
|
|
48
48
|
end
|
|
49
49
|
end
|
|
50
50
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sevgi
|
|
4
|
+
class Executor
|
|
5
|
+
# Describes the outcome of one executor invocation.
|
|
6
|
+
#
|
|
7
|
+
# A successful result has a value and no error. A captured script, file, or
|
|
8
|
+
# library failure has an {Executor::Error} and can retain a value produced
|
|
9
|
+
# before the failure. The source stack is an immutable snapshot in load
|
|
10
|
+
# order. It is never shared with the executor's internal mutable state.
|
|
11
|
+
#
|
|
12
|
+
# @example Inspect successful execution
|
|
13
|
+
# result = Sevgi.execute("6 * 7")
|
|
14
|
+
# result.success? #=> true
|
|
15
|
+
# result.value #=> 42
|
|
16
|
+
#
|
|
17
|
+
# @example Inspect a captured failure
|
|
18
|
+
# result = Sevgi.execute("missing", file: "drawing.sevgi")
|
|
19
|
+
# result.error? #=> true
|
|
20
|
+
# result.error.cause #=> #<NameError ...>
|
|
21
|
+
# result.stack #=> ["drawing.sevgi"]
|
|
22
|
+
#
|
|
23
|
+
# @see Sevgi.execute
|
|
24
|
+
# @see Sevgi.execute_file
|
|
25
|
+
# @see https://sevgi.roktas.dev/usage/#execute Execute source guide
|
|
26
|
+
Result = Data.define(:value, :error, :stack) do
|
|
27
|
+
# @!attribute [r] value
|
|
28
|
+
# @return [Object, nil] last value produced, or nil when no value was produced
|
|
29
|
+
# @!attribute [r] error
|
|
30
|
+
# @return [Sevgi::Executor::Error, nil] captured failure, or nil after successful execution
|
|
31
|
+
# @!attribute [r] stack
|
|
32
|
+
# @return [Array<String>] frozen owned source-path snapshot in load order
|
|
33
|
+
|
|
34
|
+
# Creates an execution result.
|
|
35
|
+
# @param value [Object, nil] last value produced before execution finished
|
|
36
|
+
# @param error [Sevgi::Executor::Error, nil] captured execution failure
|
|
37
|
+
# @param stack [Array<String>] source files visited in load order
|
|
38
|
+
# @return [void]
|
|
39
|
+
def initialize(value:, error:, stack:)
|
|
40
|
+
super(value:, error:, stack: Array(stack).map { it.dup.freeze }.freeze)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private_class_method :[]
|
|
44
|
+
|
|
45
|
+
# Reports whether execution completed without a captured error.
|
|
46
|
+
# @return [Boolean] true when execution succeeded
|
|
47
|
+
def success? = error.nil?
|
|
48
|
+
|
|
49
|
+
# Reports whether execution captured an error.
|
|
50
|
+
# @return [Boolean] true when execution failed
|
|
51
|
+
def error? = !success?
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
data/lib/sevgi/executor/scope.rb
CHANGED
|
@@ -8,12 +8,15 @@ module Sevgi
|
|
|
8
8
|
# fiber's executor stack. They are not shared between concurrent executions.
|
|
9
9
|
# @api private
|
|
10
10
|
class Scope
|
|
11
|
+
MAX_LOAD_DEPTH = 128
|
|
12
|
+
private_constant :MAX_LOAD_DEPTH
|
|
13
|
+
|
|
11
14
|
# @!attribute [r] scope
|
|
12
15
|
# @return [Module] isolated module where script source is evaluated
|
|
13
16
|
# @!attribute [r] recent
|
|
14
17
|
# @return [Object, nil] last expression result from the executed source
|
|
15
18
|
# @!attribute [r] error
|
|
16
|
-
# @return [
|
|
19
|
+
# @return [Exception, nil] captured execution error
|
|
17
20
|
attr_reader :scope, :recent, :error
|
|
18
21
|
|
|
19
22
|
# Creates a script execution scope.
|
|
@@ -35,7 +38,7 @@ module Sevgi
|
|
|
35
38
|
|
|
36
39
|
# Executes one source object in this scope.
|
|
37
40
|
# @param source [Sevgi::Executor::Source] source to evaluate
|
|
38
|
-
# @param receiver [Object, nil] receiver
|
|
41
|
+
# @param receiver [Object, nil] explicit boot receiver, or nil to use the isolated scope module
|
|
39
42
|
# @yield optional boot block that installs DSL methods before evaluation
|
|
40
43
|
# @yieldreturn [void]
|
|
41
44
|
# @return [Sevgi::Executor::Scope] self, with recent or error populated
|
|
@@ -54,7 +57,7 @@ module Sevgi
|
|
|
54
57
|
# @api private
|
|
55
58
|
def capture(source, error)
|
|
56
59
|
push(source)
|
|
57
|
-
@error =
|
|
60
|
+
@error = error
|
|
58
61
|
self
|
|
59
62
|
end
|
|
60
63
|
|
|
@@ -77,6 +80,16 @@ module Sevgi
|
|
|
77
80
|
# @note The stack is owned by this scope and is not shared with concurrent executions.
|
|
78
81
|
def stack = @stack.keys
|
|
79
82
|
|
|
83
|
+
# Builds the immutable public result for this scope.
|
|
84
|
+
# @return [Sevgi::Executor::Result] execution result snapshot
|
|
85
|
+
# @api private
|
|
86
|
+
def result
|
|
87
|
+
sources = stack.freeze
|
|
88
|
+
error = Executor::Error.new(@error, sources) if @error
|
|
89
|
+
|
|
90
|
+
Result.new(value: recent, error:, stack: sources)
|
|
91
|
+
end
|
|
92
|
+
|
|
80
93
|
# Returns the most recently pushed source.
|
|
81
94
|
# @return [Sevgi::Executor::Source, nil] most recent source object
|
|
82
95
|
# @api private
|
|
@@ -87,7 +100,8 @@ module Sevgi
|
|
|
87
100
|
def boot(receiver, &boot)
|
|
88
101
|
return unless boot
|
|
89
102
|
|
|
90
|
-
|
|
103
|
+
receiver = scope if receiver.nil?
|
|
104
|
+
receiver.public_send(receiver.is_a?(::Module) ? :module_exec : :instance_exec, &boot)
|
|
91
105
|
end
|
|
92
106
|
|
|
93
107
|
def evaluate(source)
|
|
@@ -104,8 +118,13 @@ module Sevgi
|
|
|
104
118
|
end
|
|
105
119
|
|
|
106
120
|
def enter(source)
|
|
107
|
-
if @active.key?(source.identity)
|
|
108
|
-
|
|
121
|
+
raise Executor::CycleError, "Recursive Sevgi load: #{source.file}" if @active.key?(source.identity)
|
|
122
|
+
|
|
123
|
+
if @active.size >= MAX_LOAD_DEPTH
|
|
124
|
+
raise(
|
|
125
|
+
Executor::LoadDepthError,
|
|
126
|
+
"Sevgi load nesting too deep (maximum #{MAX_LOAD_DEPTH} active sources): #{source.file}"
|
|
127
|
+
)
|
|
109
128
|
end
|
|
110
129
|
|
|
111
130
|
@active[source.identity] = source
|
|
@@ -121,11 +140,10 @@ module Sevgi
|
|
|
121
140
|
def execute(source, receiver, &boot)
|
|
122
141
|
active = enter(source)
|
|
123
142
|
@recent = run(source, receiver, &boot)
|
|
124
|
-
# rubocop:disable Lint/RescueException
|
|
143
|
+
# rubocop:disable-next Lint/RescueException
|
|
125
144
|
rescue Exception => e
|
|
126
|
-
@error =
|
|
145
|
+
@error = e
|
|
127
146
|
throw(:result, self)
|
|
128
|
-
# rubocop:enable Lint/RescueException
|
|
129
147
|
ensure
|
|
130
148
|
leave(active)
|
|
131
149
|
end
|
|
@@ -4,38 +4,47 @@ module Sevgi
|
|
|
4
4
|
class Executor
|
|
5
5
|
# Describes Ruby source evaluated by the Sevgi executor.
|
|
6
6
|
# @api private
|
|
7
|
-
Source = Data.define(:string, :file, :line) do
|
|
8
|
-
# @overload call(string:, file: nil, line: nil)
|
|
7
|
+
Source = Data.define(:string, :file, :line, :origin) do
|
|
8
|
+
# @overload call(string:, file: nil, line: nil, origin: nil)
|
|
9
9
|
# Builds a source object.
|
|
10
10
|
# @param string [String] Ruby source string
|
|
11
11
|
# @param file [String, nil] source file name for diagnostics
|
|
12
12
|
# @param line [Integer, nil] starting source line for diagnostics
|
|
13
|
+
# @param origin [String, nil] physical source path used for load-cycle identity
|
|
13
14
|
# @return [Sevgi::Executor::Source] source object
|
|
14
15
|
def self.call(...) = new(...)
|
|
15
16
|
|
|
16
17
|
# Builds a source object from a file.
|
|
17
18
|
# @param file [String] source file to read
|
|
19
|
+
# @param as [String, nil] logical source name used for evaluation and diagnostics
|
|
18
20
|
# @return [Sevgi::Executor::Source] source object with file contents
|
|
19
21
|
# @raise [Errno::ENOENT] when the file cannot be read
|
|
20
|
-
def self.load(file) = new(string: ::File.read(file), file: file, line: 1)
|
|
22
|
+
def self.load(file, as: nil) = new(string: ::File.read(file), file: as || file, line: 1, origin: file)
|
|
21
23
|
|
|
22
24
|
# Creates a source object.
|
|
23
25
|
# @param string [String] Ruby source string
|
|
24
26
|
# @param file [String, nil] source file name for diagnostics
|
|
25
27
|
# @param line [Integer, nil] starting source line for diagnostics
|
|
28
|
+
# @param origin [String, nil] physical source path used for load-cycle identity
|
|
26
29
|
# @return [void]
|
|
27
|
-
def initialize(string:, file: nil, line: nil
|
|
30
|
+
def initialize(string:, file: nil, line: nil, origin: nil)
|
|
31
|
+
super(string:, file: file || "sevgi", line: line || 1, origin:)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private_class_method :[]
|
|
28
35
|
|
|
29
36
|
# Returns the stack key used for this source.
|
|
30
37
|
# @return [String] source file name
|
|
31
38
|
def key = file
|
|
32
39
|
|
|
33
40
|
# Returns the canonical identity used for active-load cycle detection.
|
|
34
|
-
# @return [String]
|
|
41
|
+
# @return [Integer, String] object identity for inline source, or canonical physical identity for loaded source
|
|
35
42
|
def identity
|
|
36
|
-
|
|
43
|
+
return object_id unless origin
|
|
44
|
+
|
|
45
|
+
::File.realpath(origin)
|
|
37
46
|
rescue ::SystemCallError
|
|
38
|
-
::File.expand_path(
|
|
47
|
+
::File.expand_path(origin)
|
|
39
48
|
end
|
|
40
49
|
end
|
|
41
50
|
end
|