@@ -5,20 +5,21 @@ import java.nio.file.{ Files, LinkOption, Path, Paths }
5
5
import java .util .Scanner
6
6
import scala .jdk .CollectionConverters .IterableHasAsScala
7
7
import scala .util .Using
8
- import de .martenschaefer .data .serialization .{ AlternativeError , Codec , Element , ElementError , ElementNode , JsonCodecs , RecordParseError , ValidationError }
8
+ import de .martenschaefer .data .Result
9
+ import de .martenschaefer .data .serialization .{ AlternativeError , Codec , Element , ElementError , ElementNode , Encoder , JsonCodecs , RecordParseError , ValidationError }
9
10
import de .martenschaefer .data .serialization .JsonCodecs .given
10
11
import de .martenschaefer .data .util .DataResult .*
11
- import de .martenschaefer .data .util .Lifecycle
12
+ import de .martenschaefer .data .util .*
12
13
import feature .ConfiguredFeature
13
14
import util .*
14
15
15
16
object FeatureUpdater {
16
- def process [T : Codec ](originPath : Path , targetPath : Path ,
17
+ def process [T : Codec ](inputPath : Path , outputPath : Path ,
17
18
processor : T => ProcessResult [T ],
18
19
getPostProcessWarnings : T => List [ElementError ],
19
- fileNameRegex : String )(using flags : Flags ): Unit = {
20
- if (originPath .equals(targetPath ) && ! Flag .AssumeYes .get) {
21
- println(colored(" Input and output path are the same. The origin files will be overwritten." , Console .YELLOW ))
20
+ fileNameRegex : String , featureType : Option [ String ] )(using flags : Flags ): Option [ WarningType ] = {
21
+ if (inputPath .equals(outputPath ) && ! Flag .AssumeYes .get) {
22
+ println(colored(" Input and output path are the same. The input files will be overwritten." , Console .YELLOW ))
22
23
println(" Continue (y / N)? " )
23
24
24
25
Using (new Scanner (System .in)) { scanner =>
@@ -30,26 +31,18 @@ object FeatureUpdater {
30
31
}
31
32
32
33
println(colored(" Aborting." , Console .YELLOW ))
33
- return
34
+ return None
34
35
}
35
36
}
36
37
}
37
38
38
- if (Files .isDirectory(originPath)) {
39
- val warningType = this .processDirectory(originPath, targetPath, originPath,
40
- processor, getPostProcessWarnings, fileNameRegex)
41
-
42
- val notice = warningType match {
43
- case WarningType .Error =>
44
- colored(" Done with errors." , WarningType .Error .color)
45
- case WarningType .Warning =>
46
- colored(" Done with warnings." , WarningType .Warning .color)
47
- case _ => colored(" Done." , WarningType .Okay .color)
48
- }
49
-
50
- println(notice)
51
- } else if (Files .isRegularFile(originPath))
52
- this .processFeatureFile(originPath, targetPath, processor, getPostProcessWarnings)
39
+ if (Files .isDirectory(inputPath)) {
40
+ Some (this .processDirectory(inputPath, outputPath, inputPath,
41
+ processor, getPostProcessWarnings, fileNameRegex, featureType))
42
+ } else if (Files .isRegularFile(inputPath))
43
+ Some (this .processFeatureFile(inputPath, outputPath, processor, getPostProcessWarnings).warningType)
44
+ else
45
+ None
53
46
}
54
47
55
48
def processFeatureFile [T : Codec ](originFile : Path , targetFile : Path ,
@@ -68,27 +61,30 @@ object FeatureUpdater {
68
61
result
69
62
}
70
63
71
- def processDirectory [T : Codec ](originDirectory : Path , targetDirectory : Path ,
64
+ def processDirectory [T : Codec ](inputDirectory : Path , outputDirectory : Path ,
72
65
startingDirectory : Path ,
73
66
processor : T => ProcessResult [T ],
74
67
getPostProcessWarnings : T => List [ElementError ],
75
- fileNameRegex : String , recursive : Boolean = false )(using flags : Flags ): WarningType = {
76
- if (! Files .exists(originDirectory) || ! Files .isDirectory(originDirectory))
77
- throw new IllegalArgumentException (s " ${originDirectory.getFileName} doesn't exist or is not a directory " )
68
+ fileNameRegex : String , featureType : Option [String ],
69
+ recursive : Boolean = false )(using flags : Flags ): WarningType = {
70
+ if (! Files .exists(inputDirectory) || ! Files .isDirectory(inputDirectory))
71
+ throw new IllegalArgumentException (s " ${inputDirectory.getFileName} doesn't exist or is not a directory " )
78
72
79
- if (Files .exists(targetDirectory ) && ! Files .isDirectory(targetDirectory ))
80
- throw new IllegalArgumentException (s " ${targetDirectory .getFileName} is not a directory " )
73
+ if (Files .exists(outputDirectory ) && ! Files .isDirectory(outputDirectory ))
74
+ throw new IllegalArgumentException (s " ${outputDirectory .getFileName} is not a directory " )
81
75
82
- Files .createDirectories(targetDirectory )
76
+ Files .createDirectories(outputDirectory )
83
77
84
78
var foundWarnings = false
85
79
var foundErrors = false
86
80
87
- Using (Files .newDirectoryStream(originDirectory)) { directoryStream =>
81
+ val featureTypeString = featureType.map(_ + " " ).getOrElse(" " )
82
+
83
+ Using (Files .newDirectoryStream(inputDirectory)) { directoryStream =>
88
84
for (path <- directoryStream.asScala) {
89
85
if (Flag .Recursive .get && Files .isDirectory(path)) {
90
- processDirectory(path, targetDirectory .resolve(path.getFileName), startingDirectory,
91
- processor, getPostProcessWarnings, fileNameRegex, recursive = true ) match {
86
+ processDirectory(path, outputDirectory .resolve(path.getFileName), startingDirectory,
87
+ processor, getPostProcessWarnings, fileNameRegex, featureType, recursive = true ) match {
92
88
case WarningType .Error => foundErrors = true
93
89
case WarningType .Warning => foundWarnings = true
94
90
case _ =>
@@ -98,9 +94,9 @@ object FeatureUpdater {
98
94
if (path.getFileName.toString.matches(fileNameRegex)) {
99
95
val relativePath = startingDirectory.relativize(path)
100
96
101
- println(s " [info] Processing $relativePath" )
97
+ println(s " [info] Processing $featureTypeString$ relativePath" )
102
98
103
- val result = processFile[T ](path, targetDirectory .resolve(path.getFileName), processor, getPostProcessWarnings)
99
+ val result = processFile[T ](path, outputDirectory .resolve(path.getFileName), processor, getPostProcessWarnings)
104
100
105
101
result match {
106
102
case FileProcessResult .Errors (errors) => println()
@@ -122,39 +118,87 @@ object FeatureUpdater {
122
118
else WarningType .Okay
123
119
}
124
120
125
- def processFile [T : Codec ](originFile : Path , targetFile : Path ,
126
- processor : T => ProcessResult [T ],
127
- getPostProcessWarnings : T => List [ElementError ])(using Flags ): FileProcessResult = {
128
- var lifecycle = Lifecycle .Stable
129
- var foundWarnings = false
130
- var warnings : List [ElementError ] = List .empty
131
-
132
- val originString : String = try {
133
- read(originFile)
121
+ def readFeature [T : Codec ](inputPath : Path ): Result [T ] = {
122
+ val inputString : String = try {
123
+ read(inputPath)
134
124
} catch {
135
125
case e : IOException => {
136
- warnings ::= createErrorForException(" read" , Some (" IO" ), e)
137
- return FileProcessResult .Errors (warnings)
126
+ return Failure (List (createErrorForException(" read" , Some (" IO" ), e)))
138
127
}
139
128
140
129
case e : Exception => {
141
- warnings ::= createErrorForException(" read" , None , e)
142
- return FileProcessResult .Errors (warnings)
130
+ return Failure (List (createErrorForException(" read" , None , e)))
131
+ }
132
+ }
133
+
134
+ try {
135
+ Codec [T ].decode(inputString)
136
+ } catch {
137
+ case e : Exception => {
138
+ Failure (List (createErrorForException(" decoding" , None , e)))
143
139
}
144
140
}
141
+ }
145
142
146
- val originFeature : T = try {
147
- Codec [T ].decode(originString) match {
148
- case Success (feature, l) => lifecycle += l
149
- feature
150
- case Failure (errors, _) => return FileProcessResult .Errors (errors.toList)
143
+ def encodeFeature [T , E ](feature : T )(using Codec [T ], Encoder [Element , E ]): Result [E ] = {
144
+ try {
145
+ Codec [T ].encode(feature)
146
+ } catch {
147
+ case e : Exception => {
148
+ Failure (List (createErrorForException(" encoding" , None , e)))
151
149
}
150
+ }
151
+ }
152
+
153
+ def writeFeature (targetPath : Path , feature : String ): Option [List [ElementError ]] = {
154
+ try {
155
+ write(targetPath, feature)
156
+ None
152
157
} catch {
158
+ case e : IOException => {
159
+ Some (List (createErrorForException(" write" , None , e)))
160
+ }
161
+
153
162
case e : Exception => {
154
- warnings ::= createErrorForException(" decoding" , None , e)
155
- return FileProcessResult .Errors (warnings)
163
+ Some (List (createErrorForException(" write" , None , e)))
156
164
}
157
165
}
166
+ }
167
+
168
+ def getWarningForLifecycle (lifecycle : Lifecycle ): Option [ElementError ] = lifecycle match {
169
+ case Lifecycle .Internal =>
170
+ Some (ValidationError (_ => " Implementation details of Worldgen Updater are used." ))
171
+ case Lifecycle .Experimental =>
172
+ Some (ValidationError (_ => " Experimental features are used." ))
173
+ case Lifecycle .Deprecated (since) =>
174
+ Some (ValidationError (_ => s " Features that are deprecated since v $since are used. " ))
175
+ case _ => None
176
+ }
177
+
178
+ def printDone (warningType : WarningType )(using Flags ): Unit = {
179
+ println(warningType match {
180
+ case WarningType .Error =>
181
+ colored(" Done with errors." , WarningType .Error .color)
182
+ case WarningType .Warning =>
183
+ colored(" Done with warnings." , WarningType .Warning .color)
184
+ case _ => colored(" Done." , WarningType .Okay .color)
185
+ })
186
+ }
187
+
188
+ def processFile [T : Codec ](inputPath : Path , outputPath : Path ,
189
+ processor : T => ProcessResult [T ],
190
+ getPostProcessWarnings : T => List [ElementError ])(using Flags ): FileProcessResult = {
191
+ var lifecycle = Lifecycle .Stable
192
+ var foundWarnings = false
193
+
194
+ val originFeature : T = readFeature(inputPath) match {
195
+ case Success (feature, l) => lifecycle = l
196
+ feature
197
+
198
+ case Failure (errors, _) => return FileProcessResult .Errors (errors)
199
+ }
200
+
201
+ var warnings : List [ElementError ] = List .empty
158
202
159
203
val targetFeature : T = try {
160
204
val targetFeatureWriter : ProcessResult [T ] = processor(originFeature)
@@ -170,51 +214,19 @@ object FeatureUpdater {
170
214
}
171
215
}
172
216
173
- if (warnings.nonEmpty) foundWarnings = true
174
-
175
- val targetFeatureString : String = try {
176
- Codec [T ].encode(targetFeature)(using JsonCodecs .prettyJsonEncoder) match {
177
- case Success (json, l) => lifecycle += l
178
- json
179
- case Failure (errors, _) => {
180
- return FileProcessResult .Errors (errors.toList)
181
- }
182
- }
183
- } catch {
184
- case e : Exception => {
185
- warnings ::= createErrorForException(" encoding" , None , e)
186
- return FileProcessResult .Errors (warnings)
187
- }
188
- }
189
-
190
- lifecycle match {
191
- case Lifecycle .Internal =>
192
- warnings = ValidationError (_ => " Implementation details of Worldgen Updater are used." ) :: warnings
193
- foundWarnings = true
194
- case Lifecycle .Experimental =>
195
- warnings = ValidationError (_ => " Experimental features are used." ) :: warnings
196
- foundWarnings = true
197
- case Lifecycle .Deprecated (since) =>
198
- warnings = ValidationError (_ => s " Features that are deprecated since v $since are used. " ) :: warnings
199
- foundWarnings = true
200
- case _ =>
217
+ val targetFeatureString : String = encodeFeature(targetFeature)(using Codec [T ], JsonCodecs .prettyJsonEncoder) match {
218
+ case Success (s, l) => lifecycle += l
219
+ s
220
+ case Failure (errors, _) => return FileProcessResult .Errors (errors)
201
221
}
202
222
203
- try {
204
- write(targetFile, targetFeatureString)
205
- } catch {
206
- case e : IOException => {
207
- warnings ::= createErrorForException(" write" , Some (" IO" ), e)
208
- return FileProcessResult .Errors (warnings)
209
- }
223
+ // Add lifecycle warning
224
+ getWarningForLifecycle(lifecycle).foreach(error => warnings = error :: warnings)
210
225
211
- case e : Exception => {
212
- warnings ::= createErrorForException(" write" , None , e)
213
- return FileProcessResult .Errors (warnings)
214
- }
215
- }
226
+ // Write feature to file
227
+ writeFeature(outputPath, targetFeatureString).foreach(errors => warnings = warnings ::: errors)
216
228
217
- if (foundWarnings ) FileProcessResult .Warnings (warnings) else FileProcessResult .Normal
229
+ if (warnings.isEmpty ) FileProcessResult .Normal else FileProcessResult .Warnings (warnings)
218
230
}
219
231
220
232
private def createErrorForException (phase : String , kind : Option [String ], e : Exception ): ElementError =
0 commit comments