Skip to content

Commit a603803

Browse files
authored
Make .ext on empty paths return "" rather than crashing with a NPE (#87)
Fixes com-lihaoyi/Ammonite#1017 This is more consistent with calling `.ext` on non-empty paths which have no extension, since those currently return `""` We also introduce a new a `lastOption` method, as a way to get the last segment in a list without throwing. Not sure if that's the right thing to do, or whether we should make `.last` return `""` (which is otherwise not a valid return value, since path segments cannot be empty). Covered with simple unit tests
1 parent c81c2b5 commit a603803

File tree

3 files changed

+36
-8
lines changed

3 files changed

+36
-8
lines changed

os/src-jvm/ResourcePath.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ResourcePath private[os](val resRoot: ResourceRoot, segments0: Array[Strin
2323
def toSource = new Source.WritableSource(getInputStream)
2424
val segments: IndexedSeq[String] = segments0
2525
type ThisType = ResourcePath
26-
def last = segments0.last
26+
def lastOpt = segments0.lastOption
2727
override def toString = resRoot.errorName + "/" + segments0.mkString("/")
2828
protected[this] def make(p: Seq[String], ups: Int) = {
2929
if (ups > 0){

os/src/Path.scala

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,14 @@ trait BasePathImpl extends BasePath{
182182
def /(chunk: PathChunk): ThisType
183183

184184
def ext = {
185-
val li = last.lastIndexOf('.')
186-
if (li == -1) ""
187-
else last.slice(li+1, last.length)
185+
lastOpt match{
186+
case None => ""
187+
case Some(lastSegment) =>
188+
val li = lastSegment.lastIndexOf('.')
189+
if (li == -1) ""
190+
else last.slice(li+1, last.length)
191+
}
192+
188193
}
189194

190195
override def baseName: String = {
@@ -193,7 +198,9 @@ trait BasePathImpl extends BasePath{
193198
else last.slice(0, li)
194199
}
195200

196-
def last: String
201+
def last: String = lastOpt.getOrElse(throw PathError.LastOnEmptyPath())
202+
203+
def lastOpt: Option[String]
197204
}
198205

199206
object PathError{
@@ -208,6 +215,9 @@ object PathError{
208215

209216
case class NoRelativePath(src: RelPath, base: RelPath)
210217
extends IAE(s"Can't relativize relative paths $src from $base")
218+
219+
case class LastOnEmptyPath()
220+
extends IAE("empty path has no last segment")
211221
}
212222

213223
/**
@@ -239,7 +249,7 @@ object FilePath {
239249
*/
240250
class RelPath private[os](segments0: Array[String], val ups: Int)
241251
extends FilePath with BasePathImpl with SegmentedPath {
242-
def last = segments.last
252+
def lastOpt = segments.lastOption
243253
val segments: IndexedSeq[String] = segments0
244254
type ThisType = RelPath
245255
require(ups >= 0)
@@ -305,7 +315,7 @@ object RelPath {
305315
*/
306316
class SubPath private[os](val segments0: Array[String])
307317
extends FilePath with BasePathImpl with SegmentedPath {
308-
def last = segments.last
318+
def lastOpt = segments.lastOption
309319
val segments: IndexedSeq[String] = segments0
310320
type ThisType = SubPath
311321
protected[this] def make(p: Seq[String], ups: Int) = {
@@ -433,7 +443,7 @@ class Path private[os](val wrapped: java.nio.file.Path)
433443
def segmentCount = wrapped.getNameCount
434444
type ThisType = Path
435445

436-
def last = wrapped.getFileName.toString
446+
def lastOpt = Option(wrapped.getFileName).map(_.toString)
437447

438448
def /(chunk: PathChunk): ThisType = {
439449
if (chunk.ups > wrapped.getNameCount) throw PathError.AbsolutePathOutsideRoot

os/test/src/PathTests.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,24 @@ object PathTests extends TestSuite{
4747
assert((base / "baseOnly").ext == "")
4848
assert((base / "baseOnly.").ext == "")
4949
}
50+
51+
test("emptyExt"){
52+
os.root.ext ==> ""
53+
os.rel.ext ==> ""
54+
os.sub.ext ==> ""
55+
os.up.ext ==> ""
56+
}
57+
58+
test("emptyLast"){
59+
intercept[PathError.LastOnEmptyPath](os.root.last).getMessage ==>
60+
"empty path has no last segment"
61+
intercept[PathError.LastOnEmptyPath](os.rel.last).getMessage ==>
62+
"empty path has no last segment"
63+
intercept[PathError.LastOnEmptyPath](os.sub.last).getMessage ==>
64+
"empty path has no last segment"
65+
intercept[PathError.LastOnEmptyPath](os.up.last).getMessage ==>
66+
"empty path has no last segment"
67+
}
5068
}
5169

5270
test("RelPath"){

0 commit comments

Comments
 (0)