-
-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow multiple segments in Stringliterals (#297)
This PR adds support for multi-segment literal strings. for example ```scala val path = root / "foo/bar" val qux = "qux" val path = root / "foo/bar" / "baz" / qux val path = root / "foo/bar" / "baz/qux" ``` it also parses `..` segments from literal as `os.up` enabling syntax like: ```scala val path = root / "foo" / ".." / "bar" // equivalent to `root / "foo" / os.up / "bar"` val path = root / "foo" / "../bar" // equivalent to `root / "foo" / os.up / "bar"` ``` non-canonical paths used in literals result in compile-time errors, suggesting usage of canonical paths or removing path segment, eg. ```scala val path = root / "foo/./bar" //suggests "foo/bar" val path = root / "foo//bar" //suggests "foo/bar" val path = root / "//foo//bar/./baz" //suggests "foo/bar/baz" val path = root / "" //suggests removing the segment val path = root / "." //suggests removing the segment val path = root / "/" //suggests removing the segment val path = root / "//" //suggests removing the segment val path = root / "/./" //suggests removing the segment ``` Note: Its not usable in os-Lib itself, due to the fact that it would lead to macro expansion in the same compilation unit as its definition. @lihaoyi there is a little bit of hacking involved: 1. There is a default implicit conversion not being a macro to be used within os library, without this we would have to add a submodule and split the whole project, I'm not even sure if its doable. 4. Needed to turn off acyclic in `Path` and particular `Macro` files (also needed to mock `acyclic.skipped` in case of `scala 3`). 5. Needed to provide another implicit conversion in `ViewBoundImplicit` trait because macros turn out to be not avaliable as implicit views, this is needed for `ArrayPathChunk` and `SeqPathChunk` to work.
- Loading branch information
1 parent
5561ad6
commit 4262d5c
Showing
7 changed files
with
246 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package os | ||
|
||
import os.PathChunk.segmentsFromStringLiteralValidation | ||
|
||
import scala.language.experimental.macros | ||
import scala.reflect.macros.blackbox | ||
import acyclic.skipped | ||
|
||
// StringPathChunkConversion is a fallback to non-macro String => PathChunk implicit conversion in case eta expansion is needed, this is required for ArrayPathChunk and SeqPathChunk | ||
trait PathChunkMacros extends StringPathChunkConversion { | ||
implicit def stringPathChunkValidated(s: String): PathChunk = | ||
macro Macros.stringPathChunkValidatedImpl | ||
} | ||
|
||
object Macros { | ||
|
||
def stringPathChunkValidatedImpl(c: blackbox.Context)(s: c.Expr[String]): c.Expr[PathChunk] = { | ||
import c.universe.{Try => _, _} | ||
|
||
s match { | ||
case Expr(Literal(Constant(literal: String))) => | ||
val stringSegments = segmentsFromStringLiteralValidation(literal) | ||
|
||
c.Expr( | ||
q"""new _root_.os.PathChunk.RelPathChunk(_root_.os.RelPath.fromStringSegments($stringSegments))""" | ||
) | ||
case nonLiteral => | ||
c.Expr( | ||
q"new _root_.os.PathChunk.StringPathChunk($nonLiteral)" | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package os | ||
|
||
import os.PathChunk.{RelPathChunk, StringPathChunk, segmentsFromStringLiteralValidation} | ||
import os.RelPath.fromStringSegments | ||
|
||
import scala.quoted.{Expr, Quotes} | ||
import acyclic.skipped | ||
|
||
// StringPathChunkConversion is a fallback to non-macro String => PathChunk implicit conversion in case eta expansion is needed, this is required for ArrayPathChunk and SeqPathChunk | ||
trait PathChunkMacros extends StringPathChunkConversion { | ||
inline implicit def stringPathChunkValidated(s: String): PathChunk = | ||
${ | ||
Macros.stringPathChunkValidatedImpl('s) | ||
} | ||
} | ||
|
||
object Macros { | ||
def stringPathChunkValidatedImpl(s: Expr[String])(using quotes: Quotes): Expr[PathChunk] = { | ||
import quotes.reflect.* | ||
|
||
s.asTerm match { | ||
case Inlined(_, _, Literal(StringConstant(literal))) => | ||
val stringSegments = segmentsFromStringLiteralValidation(literal) | ||
'{ | ||
new RelPathChunk(fromStringSegments(${ | ||
Expr(stringSegments) | ||
})) | ||
} | ||
case _ => | ||
'{ | ||
{ | ||
new StringPathChunk($s) | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package os | ||
private[os] object acyclic { | ||
|
||
/** Mocks [[\\import acyclic.skipped]] for scala 3 */ | ||
private[os] type skipped | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package os | ||
|
||
import os.PathChunk.segmentsFromString | ||
import utest.{assert => _, _} | ||
|
||
object SegmentsFromStringTests extends TestSuite { | ||
|
||
val tests = Tests { | ||
test("segmentsFromString") { | ||
def testSegmentsFromString(s: String, expected: List[String]) = { | ||
assert(segmentsFromString(s).sameElements(expected)) | ||
} | ||
|
||
testSegmentsFromString(" ", List(" ")) | ||
|
||
testSegmentsFromString("", List("")) | ||
|
||
testSegmentsFromString("""foo/bar/baz""", List("foo", "bar", "baz")) | ||
|
||
testSegmentsFromString("""/""", List("", "")) | ||
testSegmentsFromString("""//""", List("", "", "")) | ||
testSegmentsFromString("""///""", List("", "", "", "")) | ||
|
||
testSegmentsFromString("""a/""", List("a", "")) | ||
testSegmentsFromString("""a//""", List("a", "", "")) | ||
testSegmentsFromString("""a///""", List("a", "", "", "")) | ||
|
||
testSegmentsFromString("""ahs/""", List("ahs", "")) | ||
testSegmentsFromString("""ahs//""", List("ahs", "", "")) | ||
|
||
testSegmentsFromString("""ahs/aa/""", List("ahs", "aa", "")) | ||
testSegmentsFromString("""ahs/aa//""", List("ahs", "aa", "", "")) | ||
|
||
testSegmentsFromString("""/a""", List("", "a")) | ||
testSegmentsFromString("""//a""", List("", "", "a")) | ||
testSegmentsFromString("""//a/""", List("", "", "a", "")) | ||
} | ||
} | ||
} |