Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Path integration #562

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rhombus-lib/rhombus/private/amalgam/core-derived.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"check.rhm"
"maybe.rhm"
"string.rhm"
"path-object.rhm"
"when_unless.rhm"
"where.rhm"
"described_as.rhm"
Expand Down
33 changes: 33 additions & 0 deletions rhombus-lib/rhombus/private/amalgam/path-object.rhm
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#lang rhombus/private/amalgam/core

import:
"core-meta.rkt" open

use_static

export:
PathString

namespace PathString:
export:
to_path
to_complete_path

fun to_path(p :: PathString) :~ Path:
Path(p)

fun
| to_complete_path(p :: PathString):
Path.to_complete_path(p)
| to_complete_path(p :: PathString, ~relative_to: base_path :: PathString):
Path.to_complete_path(p, ~relative_to: base_path)

annot.macro 'to_path':
'converting(fun (p :: PathString) :~ Path: to_path(p))'

annot.macro
| 'to_complete_path(~relative_to: $base_path)':
'converting(fun (p :: PathString) :~ Path:
to_complete_path(p, ~relative_to: $base_path))'
| 'to_complete_path':
'converting(fun (p :: PathString) :~ Path: to_complete_path(p))'
60 changes: 55 additions & 5 deletions rhombus-lib/rhombus/private/amalgam/path-object.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@
"realm.rkt"
"call-result-key.rkt"
"define-arity.rkt"
"append-key.rkt"
"compare-key.rkt"
(submod "string.rkt" static-infos)
(submod "bytes.rkt" static-infos))
"index-result-key.rkt"
"static-info.rkt"
(submod "annotation.rkt" for-class)
(submod "bytes.rkt" static-infos)
(submod "function.rkt" for-info)
(submod "list.rkt" for-listable)
(submod "string.rkt" static-infos))

(provide (for-spaces (rhombus/namespace
#f
rhombus/bind
rhombus/annot)
Path))
Path)
(for-space rhombus/annot
PathString))

(module+ for-builtin
(provide path-method-table))
Expand All @@ -36,7 +44,8 @@
(define-primitive-class Path path
#:lift-declaration
#:no-constructor-static-info
#:instance-static-info ((#%compare ((< path<?)
#:instance-static-info ((#%append Path.extend)
(#%compare ((< path<?)
(<= path<=?)
(> path>?)
(>= path>=?)
Expand All @@ -46,12 +55,19 @@
#:translucent
#:fields
([bytes Path.bytes #,(get-bytes-static-infos)])
#:namespace-fields
([current_directory current-directory])
#:properties
()
#:methods
(bytes
extend
is_absolute
parts
read_with
string
))
to_complete_path
write_with))

(define/arity #:name Path (path c)
#:static-infos ((#%call-result #,(get-path-static-infos)))
Expand All @@ -62,12 +78,46 @@
[else (raise-argument-error* who rhombus-realm
"String || Bytes || Path" c)]))

(define-static-info-syntax current-directory
(#%function-arity 3)
(#%call-result #,(get-path-static-infos))
. #,(get-function-static-infos))

(define/method (Path.bytes s)
#:primitive (path->bytes)
#:static-infos ((#%call-result #,(get-bytes-static-infos)))
(bytes->immutable-bytes (path->bytes s)))

(define/method (Path.extend p . ss)
#:primitive (build-path)
#:static-infos ((#%call-result #,(get-path-static-infos)))
(apply build-path p ss))

(define/method (Path.is_absolute p)
#:primitive (absolute-path?)
(absolute-path? p))

(define/method (Path.parts p)
#:primitive (explode-path)
#:static-infos ((#%call-result #,(get-treelist-static-infos)))
(to-treelist #f (explode-path p)))

(define/method (Path.read_with p f)
#:primitive (call-with-input-file)
(call-with-input-file p f))

(define/method (Path.string s)
#:primitive (path->string)
#:static-infos ((#%call-result #,(get-string-static-infos)))
(string->immutable-string (path->string s)))

(define/method (Path.to_complete_path p #:relative_to [base-path (current-directory)])
#:primitive (path->complete-path)
#:static-infos ((#%call-result #,(get-path-static-infos)))
(path->complete-path p base-path))

(define/method (Path.write_with p f #:exists [exists 'error])
#:primitive (call-with-output-file)
(call-with-output-file p f #:exists exists))

(define-annotation-syntax PathString (identifier-annotation path-string? ()))
23 changes: 22 additions & 1 deletion rhombus-lib/rhombus/private/amalgam/port.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
[open_bytes Port.Output.open_bytes]
[open_string Port.Output.open_string]
[get_bytes Port.Output.get_bytes]
[get_string Port.Output.get_string])
[get_string Port.Output.get_string]
ExistsFlag)
#:properties ()
#:methods
([flush Port.Output.flush]
Expand Down Expand Up @@ -124,6 +125,16 @@
any
[any_one any-one])

(define-simple-symbol-enum ExistsFlag
error
replace
truncate
[must_truncate must-truncate]
[truncate_replace truncate/replace]
update
[can_update can-update]
append)

(define (check-input-port who ip)
(unless (input-port? ip)
(raise-argument-error* who rhombus-realm "Port.Input" ip)))
Expand All @@ -139,6 +150,11 @@
[(bstr) (open-input-bytes bstr)]
[(bstr name) (open-input-bytes bstr name)]))

(define/arity (Port.Input.open_file path)
#:primitive (open-input-file)
#:static-infos ((#%call-result #,(get-input-port-static-infos)))
(open-input-file path))

(define/arity Port.Input.open_string
#:primitive (open-input-string)
#:static-infos ((#%call-result #,(get-input-port-static-infos)))
Expand All @@ -153,6 +169,11 @@
[() (open-output-bytes)]
[(name) (open-output-bytes name)]))

(define/arity (Port.Output.open_file path #:exists [exists 'error])
#:primitive (open-output-file)
#:static-infos ((#%call-result #,(get-output-port-static-infos)))
(open-output-file path #:exists (->ExistsFlag exists)))

(define/arity Port.Output.open_string
#:primitive (open-output-string)
#:static-infos ((#%call-result #,(get-output-string-port-static-infos)))
Expand Down
5 changes: 4 additions & 1 deletion rhombus-lib/rhombus/private/amalgam/print.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@
[(display?)
(display v op)]
[else
(write v op)])]
(concat
(display "Path(" op)
(write (path->string v) op)
(display ")" op))])]
[(and (procedure? v)
(not (printer-ref v #f)))
(define name (object-name v))
Expand Down
1 change: 1 addition & 0 deletions rhombus/rhombus/scribblings/reference/appendable.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ An @deftech{appendable} value is one that supports @rhombus(++). Maps,
operator ((v1 :: ReadableString) ++ (v2 :: ReadableString))
:: String
operator ((v1 :: Bytes) ++ (v2 :: Bytes)) :: MutableBytes
operator ((v1 :: Path) ++ (v2 :: PathString)) :: Path
operator ((v1 :: Appendable) ++ (v2 :: Appendable)) :: Any
){

Expand Down
103 changes: 101 additions & 2 deletions rhombus/rhombus/scribblings/reference/path.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,33 @@ A @deftech{path} value represents a filesystem path.
"path"
Path
path.bytes()
path.extend(part, ...)
path.is_absolute()
path.parts()
path.read_with(proc)
path.string()
path.write_with(proc, ...)
)

Paths are @tech{comparable}, which means that generic operations like
@rhombus(<) and @rhombus(>) work on paths.

@doc(
annot.macro 'Path'
annot.macro 'PathString'
annot.macro 'PathString.to_path'
){

Matches a path value.
Matches a path value. The @rhombus(PathString, ~annot) annotation allows
@rhombus(ReadableString, ~annot) as well as @rhombus(Path, ~annot) values.
The @rhombus(PathString.to_path, ~annot)
@tech(~doc: guide_doc){converter annotation} allows
@rhombus(PathString, ~annot) values, but converts
@rhombus(ReadableString, ~annot) values to @rhombus(Path) values.
}

@doc(
fun Path(path :: Bytes || String || Path) :: Path
fun Path(path :: Bytes || ReadableString || Path) :: Path
){

Constructs a path given a byte string, string, or existing path. When a
Expand Down Expand Up @@ -69,6 +81,93 @@ Paths are @tech{comparable}, which means that generic operations like

}

@doc(
fun Path.extend(path :: Path,
part :: PathString | #'up | #'same, ...) :: Path
){

Creates a path given a base path and any number of sub-path
extensions. See also @rhombus(++). If @rhombus(path) is an absolute path,
the result is an absolute path, otherwise the result is a relative path.

The @rhombus(path) and each @rhombus(part) must be either a relative
path, the symbol @rhombus(#'up) (indicating the relative parent
directory), or the symbol @rhombus(#'same) (indicating the
relative current directory). For Windows paths, if @rhombus(path) is a
drive specification (with or without a trailing slash) the first
@rhombus(part) can be an absolute (driveless) path. For all platforms,
the last @rhombus(part) can be a filename.

The @rhombus(path) and @rhombus(part) arguments can be paths for
any platform. The platform for the resulting path is inferred from the
@rhombus(path) and @rhombus(part) arguments, where string arguments imply
a path for the current platform. If different arguments are for
different platforms, the @rhombus(Exn.Fail.Contract, ~class) exception
is thrown. If no argument implies a platform (i.e., all are @rhombus(#'up)
or @rhombus(#'same)), the generated path is for the current platform.

Each @rhombus(part) and @rhombus(path) can optionally end in a directory
separator. If the last @rhombus(part) ends in a separator, it is
included in the resulting path.

The @rhombus(build-path) procedure builds a path @italic{without}
checking the validity of the path or accessing the filesystem.

@examples(
def p = Path("/home/rhombus")
Path.extend(p, "shape.txt")
p.extend("shape.txt")
p ++ "shape.txt"
)

}

@doc(fun Path.is_absolute(path :: Path)){

Returns @rhombus(#true) if @rhombus(path) is an absolute path, @rhombus(#false)
otherwise. This procedure does not access the filesystem.
}

@doc(
fun Path.parts(path :: Path) :: List.of(Path || #'up || #'same)
){

Returns a list of path elements that constitute @rhombus(path).

The @rhombus(Path.parts) function computes its result in time
proportional to the length of @rhombus(path).

@examples(
def p = Path("/home/rhombus/shape.txt")
Path.parts(p)
p.parts()
)

}

@doc(
fun Path.read_with(path :: Path, read_proc :: Function.of_arity(1))
){

Opens @rhombus(path) for reading and calls @rhombus(read_proc) with the
@tech{input port}. The result of @rhombus(read_proc) is the result of
@rhombus(Path.read_with).

}

@doc(
fun Path.write_with(path :: Path,
proc :: Function.of_arity(1),
~exists: exists_flag
:: Port.Output.ExistsFlag = #'error)
){

Opens @rhombus(path) for writing and calls @rhombus(write_proc) with the
@tech{output port}. The result of @rhombus(write_proc) is the result of
@rhombus(Path.write_with).

}

@doc(
fun Path.string(path :: Path) :: String
){
Expand Down
Loading