Thanks to visit codestin.com
Credit goes to github.com

Skip to content

edadma/path

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Path

Maven Central Last Commit GitHub Scala Version ScalaJS Version Scala Native Version

A clean, modern file system library for Scala that works consistently across JVM, Scala.js, and Scala Native.

Why Path?

Java's file operations are verbose and clunky:

// Java way - verbose and error-prone
val configPath = Paths.get("config").resolve("app.conf")
if (Files.exists(configPath)) {
  val content = new String(Files.readAllBytes(configPath), StandardCharsets.UTF_8)
}

// Path way - clean and fluent
val configPath = Path("config") / "app.conf"
if (configPath.exists) {
  val content = configPath.readText()
}

Path gives you:

  • Fluent API: Chain operations naturally with /
  • Cross-platform: Same code works on JVM, JS, and Native
  • Method-based: File operations as methods, not static functions
  • Rich path manipulation: Extensions, subpaths, absolute conversion
  • Complete metadata: Permissions, file types, comparisons
  • Glob support: Built-in pattern matching for file listing
  • Type safety: Case class benefits (equality, hashing, pattern matching)

Quick Start

import io.github.edadma.path._

// Create paths naturally
val projectDir = Path("src") / "main" / "scala"
val configFile = Path("/etc") / "myapp" / "config.json"

// File operations as methods (the way it should be!)
if (configFile.exists) {
  val config = configFile.readText()
  println(s"Config: $config")
}

// Rich path manipulation
val scalaFile = Path("MyClass.scala")
println(s"Extension: ${scalaFile.extension}")           // .scala
println(s"Name: ${scalaFile.nameWithoutExtension}")     // MyClass
val javaFile = scalaFile.withExtension("java")          // MyClass.java

// Convert to absolute paths
val absolutePath = projectDir.toAbsolutePath
println(s"Absolute: $absolutePath")

// Create directory structure
val buildDir = Path("target") / "classes"
buildDir.createDirectories()

// List files with patterns
val scalaFiles = projectDir.listDirectory("*.scala")
scalaFiles.foreach { entry =>
  println(s"Found: ${entry.name}")
}

// Check file permissions and metadata
if (configFile.isReadable && !configFile.isEmpty) {
  println("Config file is readable and not empty")
}

Core Features

Path Construction and Manipulation

// Path construction and combination
val base = Path("/home/user")
val docs = base / "documents" / "projects"

// Rich path manipulation
val sourceFile = Path("/projects/myapp/src/main/scala/MyClass.scala")

// File extensions
println(sourceFile.extension)              // .scala
println(sourceFile.nameWithoutExtension)   // MyClass
val testFile = sourceFile.withExtension("test.scala")  // MyClass.test.scala

// Path segments and subpaths
println(sourceFile.subpath(2, 5))          // src/main/scala
println(sourceFile.startsWith(Path("/projects")))  // true
println(sourceFile.endsWith(Path("MyClass.scala"))) // true

// Convert relative to absolute
val relPath = Path("src/main/scala")
val absPath = relPath.toAbsolutePath     // /current/working/dir/src/main/scala

// Relative paths between locations
val targetDir = Path("/home/user/projects/myapp/target")
val relative = targetDir.relativeTo(base)  // projects/myapp/target

// Path normalization
val messy = Path("src/../config/./app.conf")
val clean = messy.normalize                 // config/app.conf

// Parent and filename
val file = Path("/home/user/document.pdf")
println(file.parent)    // Some(/home/user)
println(file.filename)  // document.pdf

File System Metadata

val file = Path("important-document.pdf")

// Existence and type checking
println(s"Exists: ${file.exists}")
println(s"Is file: ${file.isFile}")
println(s"Is directory: ${file.isDirectory}")
println(s"Is symbolic link: ${file.isSymbolicLink}")

// Permission checking
println(s"Readable: ${file.isReadable}")
println(s"Writable: ${file.isWritable}")
println(s"Executable: ${file.isExecutable}")

// File properties
println(s"Size: ${file.size} bytes")
println(s"Modified: ${file.lastModified}")
println(s"Empty: ${file.isEmpty}")

// Compare files
val backup = Path("document-backup.pdf")
if (file.isSameFile(backup)) {
  println("Files are identical (same inode/reference)")
}

File Operations

val file = Path("data.txt")

// Text files
file.writeText("Hello, World!")
val content = file.readText()

// Binary files  
val data = Array[Byte](1, 2, 3, 4)
file.writeBytes(data)
val bytes = file.readBytes

// Check if file/directory is empty
if (file.isEmpty) {
  println("File has no content")
}

Directory Operations

val dir = Path("build")

// Create directories
dir.createDirectories()  // Creates parents too

// List contents with filtering
val allFiles = dir.listDirectory()
val jsonFiles = dir.listDirectory("*.json")
val appFiles = dir.listDirectory("app*")

allFiles.foreach { entry =>
  val typeStr = entry.fileType match {
    case FileType.File => "FILE"
    case FileType.Directory => "DIR"
    case FileType.SymbolicLink => "LINK"
    case FileType.Other => "OTHER"
  }
  println(s"$typeStr: ${entry.name}")
}

// Check if directory is empty
if (dir.isEmpty) {
  println("Directory contains no files")
}

File Management

val source = Path("document.pdf")
val backup = Path("backup") / "document.pdf"
val archive = Path("archive") / "document.pdf"

// Copy and move operations
source.copyTo(backup)    // Copy to backup location
source.moveTo(archive)   // Move to archive

// Cleanup
backup.delete()

Advanced Path Operations

Working with Extensions

// Handle various file types
val files = Vector(
  Path("README.md"),
  Path("app.conf"),
  Path("data.json"),
  Path("script.sh"),
  Path("archive.tar.gz")
)

files.foreach { file =>
  println(s"File: ${file.filename}")
  println(s"  Extension: '${file.extension}'")
  println(s"  Without ext: '${file.nameWithoutExtension}'")
  println(s"  As backup: ${file.withExtension(file.extension + ".bak")}")
  println()
}

Path Hierarchy Navigation

val projectRoot = Path("/projects/myapp")
val sourceDir = projectRoot / "src" / "main" / "scala"
val testDir = projectRoot / "src" / "test" / "scala"

// Check relationships
println(sourceDir.startsWith(projectRoot))  // true
println(sourceDir.endsWith(Path("scala")))  // true

// Get relative paths between directories
val relativeToTest = sourceDir.relativeTo(testDir)
println(relativeToTest)  // ../../main/scala

// Extract specific path segments
val pathSegments = sourceDir.subpath(1, 4)  // projects/myapp/src

File System Analysis

def analyzeDirectory(dir: Path): Unit = {
  if (!dir.exists || !dir.isDirectory) {
    println(s"$dir is not a valid directory")
    return
  }
  
  val entries = dir.listDirectory()
  val (files, dirs, links) = entries.partition(_.fileType).fold(
    (Vector.empty, Vector.empty, Vector.empty)
  ) { case ((f, d, l), entry) =>
    entry.fileType match {
      case FileType.File => (f :+ entry, d, l)
      case FileType.Directory => (f, d :+ entry, l)
      case FileType.SymbolicLink => (f, d, l :+ entry)
      case FileType.Other => (f, d, l)
    }
  }
  
  println(s"Analysis of $dir:")
  println(s"  Files: ${files.length}")
  println(s"  Directories: ${dirs.length}")
  println(s"  Symbolic links: ${links.length}")
  println(s"  Empty: ${dir.isEmpty}")
  
  // Find largest files
  val fileSizes = files.map { entry =>
    val filePath = dir / entry.name
    (entry.name, filePath.size)
  }.sortBy(-_._2)
  
  println("  Largest files:")
  fileSizes.take(3).foreach { case (name, size) =>
    println(s"    $name: $size bytes")
  }
}

Cross-Platform Architecture

Path provides a unified API while using the best platform-specific implementations:

  • JVM: Uses java.nio.files for robust, high-performance file operations
  • Scala.js: Uses Node.js fs and path modules for full file system access
  • Scala Native: Uses Java NIO compatibility layer for native performance

The same Path code compiles and runs identically across all platforms.

Installation

Add to your build.sbt:

libraryDependencies += "io.github.edadma" %% "path" % "0.0.2"

For cross-platform projects:

// shared/src/main/scala - your cross-platform code using Path
// jvm/src/main/scala    - JVM-specific implementations  
// js/src/main/scala     - JS-specific implementations
// native/src/main/scala - Native-specific implementations

Examples

Configuration Management

val configDir = Path(System.getProperty("user.home")) / ".myapp"
configDir.createDirectories()

val configFile = configDir / "config.json"
if (!configFile.exists) {
  configFile.writeText("""{"theme": "dark", "autoSave": true}""")
}

// Validate config file
if (configFile.isReadable && !configFile.isEmpty) {
  val config = configFile.readText()
  println(s"Loaded config: ${config.length} characters")
}

Build Tool Integration

val sourceDir = Path("src") / "main" / "scala"
val targetDir = Path("target") / "classes"

// Find all Scala source files
val scalaFiles = sourceDir.listDirectory("*.scala")
println(s"Found ${scalaFiles.length} Scala files")

// Analyze source files
scalaFiles.foreach { entry =>
  val sourcePath = sourceDir / entry.name
  println(s"${sourcePath.filename}: ${sourcePath.size} bytes")
  
  // Convert to target path
  val targetPath = targetDir / sourcePath.nameWithoutExtension.withExtension("class")
  println(s"${targetPath}")
}

// Compile to target directory
targetDir.createDirectories()

Log File Management

val logDir = Path("logs")
logDir.createDirectories()

// Find and archive old logs
val logFiles = logDir.listDirectory("*.log")
val archiveDir = logDir / "archive"

if (logFiles.nonEmpty) {
  archiveDir.createDirectories()
  
  logFiles.foreach { entry =>
    val logFile = logDir / entry.name
    
    // Check if log file should be archived (e.g., older than 30 days)
    val ageMs = System.currentTimeMillis() - logFile.lastModified
    val ageDays = ageMs / (1000 * 60 * 60 * 24)
    
    if (ageDays > 30) {
      val archiveFile = archiveDir / entry.name
      println(s"Archiving ${logFile.filename} (${ageDays} days old)")
      logFile.moveTo(archiveFile)
    }
  }
}

// Clean up empty directories
if (logDir.isEmpty) {
  println("Log directory is empty")
}

File System Utilities

def findLargestFiles(dir: Path, count: Int = 10): Vector[(Path, Long)] = {
  if (!dir.exists || !dir.isDirectory) {
    return Vector.empty
  }
  
  val allFiles = dir.listDirectory()
    .filter(_.fileType == FileType.File)
    .map(entry => dir / entry.name)
    .filter(_.isReadable)
  
  allFiles
    .map(file => (file, file.size))
    .sortBy(-_._2)
    .take(count)
}

def findFilesByExtension(dir: Path, extension: String): Vector[Path] = {
  val pattern = if (extension.startsWith(".")) s"*$extension" else s"*.$extension"
  
  dir.listDirectory(pattern)
    .map(entry => dir / entry.name)
    .filter(_.isFile)
}

// Usage
val projectDir = Path(".")
val largestFiles = findLargestFiles(projectDir)
val scalaFiles = findFilesByExtension(projectDir, ".scala")

println("Largest files:")
largestFiles.foreach { case (file, size) =>
  println(s"  ${file.filename}: $size bytes")
}

println(s"\nFound ${scalaFiles.length} Scala files")

Testing

Path includes comprehensive tests covering:

  • Path construction and manipulation
  • File and directory operations
  • Extension handling and path conversion
  • Permission and metadata checking
  • Cross-platform compatibility
  • Package manager scenarios

Run tests with:

sbt test

Contributing

This library was built to solve real-world file system challenges in Scala. Contributions are welcome!

Upcoming features:

  • Watch APIs for file system monitoring
  • Streaming operations for large files
  • Advanced glob patterns with recursion
  • File system event notifications

License

ISC License - see LICENSE file for details.


Finally, a file system library that doesn't make you hate working with files in Scala.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages