1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
package cbt.file
import java.io.File
import java.nio.file.Files._
import java.nio.file.StandardCopyOption._
import cbt.common_1._
object `package` extends Module {
implicit class CbtFileOps( val file: File ) extends ops.CbtFileOps
}
package ops {
trait CbtFileOps extends Any {
def file: File
def ++( s: String ): File = {
if ( s endsWith "/" ) throw new Exception(
"""Trying to append a String that ends in "/" to a File would loose the trailing "/". Use .stripSuffix("/") if you need to."""
)
new File( string + s ) // PERFORMANCE HOTSPOT
}
def /( s: String ): File = {
new File( file, s )
}
def parent = realpath( file / ".." )
def string = file.toString
def listOrFail: Seq[File] = Option( file.listFiles ).getOrElse( throw new Exception( "no such file: " + file ) ).toVector
def listRecursive: Seq[File] =
file +: (
if ( file.isDirectory ) file.listOrFail.flatMap( _.listRecursive ).toVector else Vector[File]()
)
def lastModifiedRecursive = listRecursive.map( _.lastModified ).max
def readAsString = new String( readAllBytes( file.toPath ) )
def quote = s"""new _root_.java.io.File(${string.quote})"""
}
}
trait Module {
def realpath( name: File ) = new File( java.nio.file.Paths.get( name.getAbsolutePath ).normalize.toString )
def transformFiles( files: Seq[File], transform: String => String ): Seq[File] = {
transformFilesOrError( files, s => Right( transform( s ) ) )._1
}
def transformFilesOrError[T]( files: Seq[File], transform: String => Either[T, String] ): ( Seq[File], Seq[( File, T )] ) = {
val results = files.map { file =>
val string = file.readAsString
transform( string ).left.map(
file -> _
).right.map(
replaced =>
if ( string != replaced ) {
val tmpFile = file ++ ".cbt-tmp"
assert( !tmpFile.exists )
write( tmpFile.toPath, replaced.getBytes )
move( tmpFile.toPath, file.toPath, REPLACE_EXISTING )
Some( file )
} else None
)
}
( results.map( _.right.toOption ).flatten.flatten, results.map( _.left.toOption ).flatten )
}
def autoRelative(
files: Seq[File], collector: PartialFunction[( File, String ), String] = { case ( _, r ) => r },
allowDuplicates: Boolean = false
): Seq[( File, String )] = {
val map = files.sorted.flatMap { base =>
val b = base.getCanonicalFile.string
if ( base.isDirectory ) {
base.listRecursive.map { f =>
f -> f.getCanonicalFile.string.stripPrefix( b ).stripPrefix( File.separator )
}
} else {
Seq( base -> base.getName )
}
}.collect {
case v @ ( file, _ ) if collector.isDefinedAt( v ) => file -> collector( v )
}
if ( !allowDuplicates ) {
val relatives = map.unzip._2
val duplicateFiles = ( relatives diff relatives.distinct ).distinct
assert(
duplicateFiles.isEmpty, {
val rs = relatives.toSet
"Conflicting:\n\n" +
map.filter( rs contains _._2 ).groupBy( _._2 ).mapValues( _.map( _._1 ).sorted ).toSeq.sortBy( _._1 ).map {
case ( name, files ) => s"$name:\n" ++ files.mkString( "\n" )
}.mkString( "\n\n" )
}
)
}
map
}
/* recursively deletes folders*/
def deleteRecursive( file: File ): Unit = {
val s = file.string
// some desperate attempts to keep people from accidentally deleting their hard drive
assert( file === file.getCanonicalFile, "deleteRecursive requires previous .getCanonicalFile" )
assert( file.isAbsolute, "deleteRecursive requires absolute path" )
assert( file.string != "", "deleteRecursive requires non-empty file path" )
assert( s.split( File.separator.replace( "\\", "\\\\" ) ).size > 4, "deleteRecursive requires absolute path of at least depth 4" )
assert( !file.listRecursive.exists( _.isHidden ), "deleteRecursive requires no files to be hidden" )
assert( file.listRecursive.forall( _.canWrite ), "deleteRecursive requires all files to be writable" )
if ( file.isDirectory ) {
file.listOrFail.map( deleteRecursive )
}
file.delete
}
}
|