mirror of
https://github.com/erg-lang/erg.git
synced 2025-08-04 18:58:30 +00:00
refactor(pystd): use methods declaration syntax
This commit is contained in:
parent
7e9cef9c07
commit
66ece61af2
13 changed files with 164 additions and 144 deletions
|
@ -166,7 +166,7 @@ impl<Checker: BuildRunnable> Server<Checker> {
|
|||
) -> ELSResult<()> {
|
||||
if let Some(module) = def_loc.module.as_ref() {
|
||||
let mut def_pos = match util::loc_to_range(def_loc.loc) {
|
||||
Some(range) => range.start,
|
||||
Some(range) => range.end,
|
||||
None => {
|
||||
return Ok(());
|
||||
}
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
.ArgumentParser!: ClassType
|
||||
.new = 'ArgumentParser': (description := Str, prog := Str) -> .ArgumentParser!
|
||||
.ArgumentParser!.add_argument!: (
|
||||
name: Str, # TODO: var-args
|
||||
action := Str or NoneType,
|
||||
default := Obj or NoneType,
|
||||
type := Type,
|
||||
required := Bool,
|
||||
help := Str or NoneType,
|
||||
choices := Obj or NoneType,
|
||||
) => ._StoreAction
|
||||
.ArgumentParser!.parse_args!: (args := Str or [Str; _] or NoneType,) => Obj # TODO: type with dependent types
|
||||
.ArgumentParser!.
|
||||
add_argument!: (
|
||||
name: Str, # TODO: var-args
|
||||
action := Str or NoneType,
|
||||
default := Obj or NoneType,
|
||||
type := Type,
|
||||
required := Bool,
|
||||
help := Str or NoneType,
|
||||
choices := Obj or NoneType,
|
||||
) => ._StoreAction
|
||||
.parse_args!: (args := Str or [Str; _] or NoneType,) => Obj # TODO: type with dependent types
|
||||
|
|
|
@ -4,55 +4,62 @@ time = pyimport "time"
|
|||
.MAXYEAR: {9999}
|
||||
|
||||
.TimeDelta = 'timedelta': ClassType
|
||||
.TimeDelta.__call__: (days := Nat, seconds := Nat, microseconds := Nat, milliseconds := Nat, minutes := Nat, hours := Nat, weeks := Nat) -> .TimeDelta
|
||||
.TimeDelta.min: .TimeDelta
|
||||
.TimeDelta.max: .TimeDelta
|
||||
.TimeDelta.resolution: .TimeDelta
|
||||
.TimeDelta.total_seconds: (self: .TimeDelta) -> Float
|
||||
.TimeDelta.
|
||||
__call__: (days := Nat, seconds := Nat, microseconds := Nat, milliseconds := Nat, minutes := Nat, hours := Nat, weeks := Nat) -> .TimeDelta
|
||||
min: .TimeDelta
|
||||
max: .TimeDelta
|
||||
resolution: .TimeDelta
|
||||
total_seconds: (self: .TimeDelta) -> Float
|
||||
.Date = 'date': ClassType
|
||||
.Date.__call__: (year: Nat, month: Nat, day: Nat) -> .Date
|
||||
.Date.fromtimestamp: (timestamp: Float) -> .Date
|
||||
.Date.fromordinal: (ordinal: Nat) -> .Date
|
||||
.Date.fromisoformat: (date_string: Str) -> .Date
|
||||
.Date.fromisocalendar: (year: Nat, week: Nat, day: Nat) -> .Date
|
||||
.Date.replace: (self: .Date, year := Nat, month := Nat, day := Nat) -> .Date
|
||||
.Date.timetuple: (self: .Date) -> time.StructTime
|
||||
.Date.toordinal: (self: .Date) -> Nat
|
||||
.Date.weekday: (self: .Date) -> 0..6
|
||||
.Date.isoweekday: (self: .Date) -> 1..7
|
||||
.Date.isocalendar: (self: .Date) -> {year = Nat; week = Nat; weekday = 1..7}
|
||||
.Date.isoformat: (self: .Date) -> Str
|
||||
.Date.strftime: (self: .Date, format: Str) -> Str
|
||||
.Date.today!: () => .Date
|
||||
.Date.min: .Date
|
||||
.Date.max: .Date
|
||||
.Date.resolution: .TimeDelta
|
||||
.Date.
|
||||
__call__: (year: Nat, month: Nat, day: Nat) -> .Date
|
||||
fromtimestamp: (timestamp: Float) -> .Date
|
||||
fromordinal: (ordinal: Nat) -> .Date
|
||||
fromisoformat: (date_string: Str) -> .Date
|
||||
fromisocalendar: (year: Nat, week: Nat, day: Nat) -> .Date
|
||||
replace: (self: .Date, year := Nat, month := Nat, day := Nat) -> .Date
|
||||
timetuple: (self: .Date) -> time.StructTime
|
||||
toordinal: (self: .Date) -> Nat
|
||||
weekday: (self: .Date) -> 0..6
|
||||
isoweekday: (self: .Date) -> 1..7
|
||||
isocalendar: (self: .Date) -> {year = Nat; week = Nat; weekday = 1..7}
|
||||
isoformat: (self: .Date) -> Str
|
||||
strftime: (self: .Date, format: Str) -> Str
|
||||
'''
|
||||
Current date or datetime: same as `self.__class__.fromtimestamp(time.time())`.
|
||||
'''
|
||||
today!: () => .Date
|
||||
min: .Date
|
||||
max: .Date
|
||||
resolution: .TimeDelta
|
||||
.TZInfo = 'tzinfo': ClassType
|
||||
.Time = 'time': ClassType
|
||||
.Time.__call__: (hour: Nat, minute: Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .Time
|
||||
.Time.min: .Time
|
||||
.Time.max: .Time
|
||||
.Time.resolution: .TimeDelta
|
||||
.Time.fromisoformat: (time_string: Str) -> .Time
|
||||
.Time.replace: (self: .Time, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .Time
|
||||
.Time.isoformat: (self: .Time, timespec := Str) -> Str
|
||||
.Time.
|
||||
__call__: (hour: Nat, minute: Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .Time
|
||||
min: .Time
|
||||
max: .Time
|
||||
resolution: .TimeDelta
|
||||
fromisoformat: (time_string: Str) -> .Time
|
||||
replace: (self: .Time, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .Time
|
||||
isoformat: (self: .Time, timespec := Str) -> Str
|
||||
.DateTime = 'dateTime': ClassType
|
||||
.DateTime.__call__: (year: Nat, month: Nat, day: Nat, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
.DateTime.today!: () => .DateTime
|
||||
.DateTime.now!: (tz := .TZInfo or NoneType) => .DateTime
|
||||
.DateTime.utcnow!: () => .DateTime
|
||||
.DateTime.fromtimestamp: (timestamp: Float, tz := .TZInfo or NoneType) -> .DateTime
|
||||
.DateTime.utcfromtimestamp: (timestamp: Float) -> .DateTime
|
||||
.DateTime.fromordinal: (ordinal: Nat) -> .DateTime
|
||||
.DateTime.combine: (date: .Date, time: .Time, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
.DateTime.fromisoformat: (date_string: Str) -> .DateTime
|
||||
.DateTime.fromisocalendar: (year: Nat, week: Nat, day: Nat) -> .DateTime
|
||||
.DateTime.strptime: (date_string: Str, format: Str) -> .DateTime
|
||||
.DateTime.min: .DateTime
|
||||
.DateTime.max: .DateTime
|
||||
.DateTime.resolution: .TimeDelta
|
||||
.DateTime.date: (self: .DateTime) -> .Date
|
||||
.DateTime.time: (self: .DateTime) -> .Time
|
||||
.DateTime.replace: (self: .DateTime, year := Nat, month := Nat, day := Nat, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
.DateTime.utcoffset: (self: .DateTime) -> .TimeDelta or NoneType
|
||||
.DateTime.
|
||||
__call__: (year: Nat, month: Nat, day: Nat, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
today!: () => .DateTime
|
||||
now!: (tz := .TZInfo or NoneType) => .DateTime
|
||||
utcnow!: () => .DateTime
|
||||
fromtimestamp: (timestamp: Float, tz := .TZInfo or NoneType) -> .DateTime
|
||||
utcfromtimestamp: (timestamp: Float) -> .DateTime
|
||||
fromordinal: (ordinal: Nat) -> .DateTime
|
||||
combine: (date: .Date, time: .Time, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
fromisoformat: (date_string: Str) -> .DateTime
|
||||
fromisocalendar: (year: Nat, week: Nat, day: Nat) -> .DateTime
|
||||
strptime: (date_string: Str, format: Str) -> .DateTime
|
||||
min: .DateTime
|
||||
max: .DateTime
|
||||
resolution: .TimeDelta
|
||||
date: (self: .DateTime) -> .Date
|
||||
time: (self: .DateTime) -> .Time
|
||||
replace: (self: .DateTime, year := Nat, month := Nat, day := Nat, hour := Nat, minute := Nat, second := Nat, microsecond := Nat, tzinfo := .TZInfo or NoneType) -> .DateTime
|
||||
utcoffset: (self: .DateTime) -> .TimeDelta or NoneType
|
||||
.TimeZone = 'timezone': ClassType
|
||||
|
|
|
@ -3,21 +3,22 @@
|
|||
.clear_cache!: () => NoneType
|
||||
|
||||
.DirCmp = 'dircmp': ClassType
|
||||
.DirCmp.__call__: (a: PathLike, b: PathLike, ignore := PathLike or NoneType, hide := PathLike or NoneType) -> .DirCmp
|
||||
.DirCmp.left: Str
|
||||
.DirCmp.right: Str
|
||||
.DirCmp.left_list: [Str; _]
|
||||
.DirCmp.right_list: [Str; _]
|
||||
.DirCmp.left_only: [Str; _]
|
||||
.DirCmp.right_only: [Str; _]
|
||||
.DirCmp.common: [Str; _]
|
||||
.DirCmp.common_dirs: [Str; _]
|
||||
.DirCmp.common_files: [Str; _]
|
||||
.DirCmp.common_funny: [Str; _]
|
||||
.DirCmp.same_files: [Str; _]
|
||||
.DirCmp.diff_files: [Str; _]
|
||||
.DirCmp.funny_files: [Str; _]
|
||||
.DirCmp.subdirs: {Str: .DirCmp}
|
||||
.DirCmp.report!: (self: .DirCmp) => NoneType
|
||||
.DirCmp.report_full_closure!: (self: .DirCmp) => NoneType
|
||||
.DirCmp.report_partial_closure!: (self: .DirCmp) => NoneType
|
||||
.DirCmp.
|
||||
__call__: (a: PathLike, b: PathLike, ignore := PathLike or NoneType, hide := PathLike or NoneType) -> .DirCmp
|
||||
left: Str
|
||||
right: Str
|
||||
left_list: [Str; _]
|
||||
right_list: [Str; _]
|
||||
left_only: [Str; _]
|
||||
right_only: [Str; _]
|
||||
common: [Str; _]
|
||||
common_dirs: [Str; _]
|
||||
common_files: [Str; _]
|
||||
common_funny: [Str; _]
|
||||
same_files: [Str; _]
|
||||
diff_files: [Str; _]
|
||||
funny_files: [Str; _]
|
||||
subdirs: {Str: .DirCmp}
|
||||
report!: (self: .DirCmp) => NoneType
|
||||
report_full_closure!: (self: .DirCmp) => NoneType
|
||||
report_partial_closure!: (self: .DirCmp) => NoneType
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.HTMLParser: ClassType
|
||||
.HTMLParser.feed!: (self: RefMut(.HTMLParser), data: Str) => NoneType
|
||||
.HTMLParser.close!: (self: .HTMLParser,) => NoneType
|
||||
.HTMLParser.reset!: (self: RefMut(.HTMLParser),) => NoneType
|
||||
.HTMLParser.
|
||||
feed!: (self: RefMut(.HTMLParser), data: Str) => NoneType
|
||||
close!: (self: .HTMLParser,) => NoneType
|
||||
reset!: (self: RefMut(.HTMLParser),) => NoneType
|
||||
|
|
|
@ -2,16 +2,18 @@
|
|||
|
||||
.StringIO!: ClassType
|
||||
.StringIO! <: FileLike!
|
||||
.StringIO!.read!: (self: RefMut(.StringIO!), ) => Str
|
||||
.StringIO!.write!: (self: RefMut(.StringIO!), s: Str) => NoneType
|
||||
.StringIO!.getvalue!: (self: Ref(.StringIO!),) => Str
|
||||
.StringIO!.
|
||||
read!: (self: RefMut(.StringIO!), ) => Str
|
||||
write!: (self: RefMut(.StringIO!), s: Str) => NoneType
|
||||
getvalue!: (self: Ref(.StringIO!),) => Str
|
||||
|
||||
.TextIOWrapper!: ClassType
|
||||
|
||||
.BytesIO!: ClassType
|
||||
.BytesIO! <: FileLike!
|
||||
.BytesIO!.read!: (self: RefMut(.BytesIO!), ) => Bytes
|
||||
.BytesIO!.write!: (self: RefMut(.BytesIO!), b: Bytes) => NoneType
|
||||
.BytesIO!.
|
||||
read!: (self: RefMut(.BytesIO!), ) => Bytes
|
||||
write!: (self: RefMut(.BytesIO!), b: Bytes) => NoneType
|
||||
.newBytesIO = 'BytesIO': (bytes: Bytes,) -> .BytesIO!
|
||||
|
||||
.open!: (file: PathLike, mode := Str, buffering := Nat, encoding := Str or NoneType) -> File!
|
||||
|
|
|
@ -7,16 +7,17 @@
|
|||
.Filter: ClassType
|
||||
|
||||
.LogRecord: ClassType
|
||||
.LogRecord.name: Str
|
||||
.LogRecord.level: Nat
|
||||
.LogRecord.pathname: Str
|
||||
.LogRecord.lineno: Nat
|
||||
.LogRecord.msg: Str
|
||||
.LogRecord.args: GenericTuple
|
||||
.LogRecord.exc_info: GenericTuple
|
||||
.LogRecord.func: Str or NoneType
|
||||
.LogRecord.sinfo: Str or NoneType
|
||||
.LogRecord.getMessage: (self: .LogRecord) -> Str
|
||||
.LogRecord.
|
||||
name: Str
|
||||
level: Nat
|
||||
pathname: Str
|
||||
lineno: Nat
|
||||
msg: Str
|
||||
args: GenericTuple
|
||||
exc_info: GenericTuple
|
||||
func: Str or NoneType
|
||||
sinfo: Str or NoneType
|
||||
getMessage: (self: .LogRecord) -> Str
|
||||
|
||||
.LoggerAdaptor: ClassType
|
||||
|
||||
|
|
|
@ -15,24 +15,26 @@
|
|||
.VERBOSE: .RegexFlag
|
||||
|
||||
.Match: ClassType
|
||||
.Match.expand: (self: .Match, template: Str) -> Str
|
||||
# TODO: tuple
|
||||
.Match.group: (self: .Match, x := Int or Str) -> Str
|
||||
.Match.__getitem__: (self: .Match, x := Int or Str) -> Str
|
||||
.Match.
|
||||
expand: (self: .Match, template: Str) -> Str
|
||||
# TODO: tuple
|
||||
group: (self: .Match, x := Int or Str) -> Str
|
||||
__getitem__: (self: .Match, x := Int or Str) -> Str
|
||||
|
||||
.Pattern: ClassType
|
||||
.Pattern.search: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
.Pattern.match: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
.Pattern.fullmatch: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
.Pattern.split: (self: .Pattern, string: Str, maxspilit := Nat) -> [Str; _]
|
||||
.Pattern.findall: (self: .Pattern, string: Str) -> [Str; _]
|
||||
# TODO: iterator
|
||||
.Pattern.finditer: (self: .Pattern, string: Str) -> [.Match; _]
|
||||
.Pattern.sub: (self: .Pattern, repl: Str, string: Str, count := Nat) -> Str
|
||||
.Pattern.subn: (self: .Pattern, repl: Str, string: Str, count := Nat) -> (Str, Nat)
|
||||
.Pattern.flags: Nat
|
||||
.Pattern.groups: Nat
|
||||
.Pattern.pattern: Str
|
||||
.Pattern.
|
||||
search: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
match: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
fullmatch: (self: .Pattern, string: Str) -> .Match or NoneType
|
||||
split: (self: .Pattern, string: Str, maxspilit := Nat) -> [Str; _]
|
||||
findall: (self: .Pattern, string: Str) -> [Str; _]
|
||||
# TODO: iterator
|
||||
finditer: (self: .Pattern, string: Str) -> [.Match; _]
|
||||
sub: (self: .Pattern, repl: Str, string: Str, count := Nat) -> Str
|
||||
subn: (self: .Pattern, repl: Str, string: Str, count := Nat) -> (Str, Nat)
|
||||
flags: Nat
|
||||
groups: Nat
|
||||
pattern: Str
|
||||
|
||||
.compile: (pattern: Str, flags := Nat or .RegexFlag) -> .Pattern
|
||||
.search: (pattern: Str, string: Str, flags := Nat or .RegexFlag) -> .Match or NoneType
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
.CompletedProcess: ClassType
|
||||
.CompletedProcess.args: Str or [Str; _]
|
||||
.CompletedProcess.returncode: Int
|
||||
.CompletedProcess.stdout: Bytes or NoneType
|
||||
.CompletedProcess.stderr: Bytes or NoneType
|
||||
.CompletedProcess.
|
||||
args: Str or [Str; _]
|
||||
returncode: Int
|
||||
stdout: Bytes or NoneType
|
||||
stderr: Bytes or NoneType
|
||||
|
||||
.run!: (
|
||||
args: Str or [Str; _],
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
.TarFile!: ClassType
|
||||
.TarFile! <: FileLike!
|
||||
.TarFile!.open!: (path: PathLike or NoneType := NoneType, mode := Str) => .TarFile!
|
||||
.TarFile!.add!: (self: RefMut(.TarFile!), name: PathLike, arcname: PathLike or NoneType := NoneType, recursive := Bool) => NoneType
|
||||
.TarFile!.close!: (self: .TarFile!,) => NoneType
|
||||
.TarFile!.extractall!: (self: RefMut(.TarFile!), path := PathLike, members: [Str; _] or NoneType := NoneType, numeric_owner := Bool) => NoneType
|
||||
.TarFile!.getnames: (self: Ref(.TarFile!),) -> [Str; _]
|
||||
.TarFile!.list: (self: Ref(.TarFile!), verbose := Bool) -> [Str; _]
|
||||
.TarFile!.
|
||||
open!: (path: PathLike or NoneType := NoneType, mode := Str) => .TarFile!
|
||||
add!: (self: RefMut(.TarFile!), name: PathLike, arcname: PathLike or NoneType := NoneType, recursive := Bool) => NoneType
|
||||
close!: (self: .TarFile!,) => NoneType
|
||||
extractall!: (self: RefMut(.TarFile!), path := PathLike, members: [Str; _] or NoneType := NoneType, numeric_owner := Bool) => NoneType
|
||||
getnames: (self: Ref(.TarFile!),) -> [Str; _]
|
||||
list: (self: Ref(.TarFile!), verbose := Bool) -> [Str; _]
|
||||
|
||||
.open!: (path: PathLike or NoneType := NoneType, mode := Str, fileobj: FileLike! or NoneType := NoneType) => .TarFile!
|
||||
.is_tarfile: (name: Str or File!,) -> Bool
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
.Thread!: ClassType
|
||||
.Thread!.name: Str
|
||||
.Thread!.daemon: Bool
|
||||
.Thread!.ident: Nat or NoneType # TODO: Pos or NoneType
|
||||
.Thread!.native_id: Nat or NoneType
|
||||
.Thread!.start!: (self: .Thread!) => NoneType
|
||||
.Thread!.run!: (self: .Thread!) => NoneType
|
||||
.Thread!.join!: (self: .Thread!, timeout := Nat or NoneType) => NoneType
|
||||
.Thread!.is_alive: (self: .Thread!) -> Bool
|
||||
.Thread!.
|
||||
name: Str
|
||||
daemon: Bool
|
||||
ident: Nat or NoneType # TODO: Pos or NoneType
|
||||
native_id: Nat or NoneType
|
||||
start!: (self: .Thread!) => NoneType
|
||||
run!: (self: .Thread!) => NoneType
|
||||
join!: (self: .Thread!, timeout := Nat or NoneType) => NoneType
|
||||
is_alive: (self: .Thread!) -> Bool
|
||||
|
||||
.Local! = 'local': ClassType
|
||||
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
.time!: () => Float
|
||||
|
||||
.StructTime = 'struct_time': ClassType
|
||||
.StructTime.tm_year: Nat
|
||||
.StructTime.tm_mon: Nat
|
||||
.StructTime.tm_mday: Nat
|
||||
.StructTime.tm_hour: Nat
|
||||
.StructTime.tm_min: Nat
|
||||
.StructTime.tm_sec: Nat
|
||||
.StructTime.tm_wday: Nat
|
||||
.StructTime.tm_yday: Nat
|
||||
.StructTime.tm_isdst: Nat
|
||||
.StructTime.tm_zone: Str
|
||||
.StructTime.tm_gmtoff: Nat
|
||||
.StructTime.
|
||||
tm_year: Nat
|
||||
tm_mon: Nat
|
||||
tm_mday: Nat
|
||||
tm_hour: Nat
|
||||
tm_min: Nat
|
||||
tm_sec: Nat
|
||||
tm_wday: Nat
|
||||
tm_yday: Nat
|
||||
tm_isdst: Nat
|
||||
tm_zone: Str
|
||||
tm_gmtoff: Nat
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
.ZipFile! <: FileLike!
|
||||
|
||||
.open! = 'ZipFile': (path: PathLike or FileLike!, mode := Str) => .ZipFile!
|
||||
.ZipFile!.open!: (name: PathLike, mode := Str) => .ZipFile!
|
||||
.ZipFile!.add!: (self: RefMut(.ZipFile!), name: PathLike, arcname: PathLike or NoneType := NoneType, recursive := Bool) => NoneType
|
||||
.ZipFile!.close!: (self: .ZipFile!,) => NoneType
|
||||
.ZipFile!.extractall!: (self: RefMut(.ZipFile!), path := PathLike, members: [Str; _] or NoneType := NoneType, numeric_owner := Bool) => NoneType
|
||||
.ZipFile!.namelist: (self: Ref(.ZipFile!),) -> [Str; _]
|
||||
.ZipFile!.
|
||||
open!: (name: PathLike, mode := Str) => .ZipFile!
|
||||
add!: (self: RefMut(.ZipFile!), name: PathLike, arcname: PathLike or NoneType := NoneType, recursive := Bool) => NoneType
|
||||
close!: (self: .ZipFile!,) => NoneType
|
||||
extractall!: (self: RefMut(.ZipFile!), path := PathLike, members: [Str; _] or NoneType := NoneType, numeric_owner := Bool) => NoneType
|
||||
namelist: (self: Ref(.ZipFile!),) -> [Str; _]
|
||||
|
||||
.is_zipfile: (name: Str or File!,) -> Bool
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue