@@ -18,6 +18,7 @@ import com.itsaky.androidide.templates.RecipeExecutor
1818import com.itsaky.androidide.templates.TemplateRecipe
1919import com.itsaky.androidide.templates.impl.base.ProjectTemplateRecipeResultImpl
2020import com.itsaky.androidide.utils.Environment
21+ import io.pebbletemplates.pebble.error.PebbleException
2122
2223import org.adfa.constants.ANDROID_GRADLE_PLUGIN_VERSION
2324import org.adfa.constants.KOTLIN_VERSION
@@ -32,6 +33,8 @@ class ZipRecipeExecutor(
3233 private val defModule : ModuleTemplateData ,
3334) : TemplateRecipe<ProjectTemplateRecipeResult> {
3435
36+ var hasErrorsWarnings: Boolean = false
37+
3538 companion object {
3639 private val log = LoggerFactory .getLogger(ZipRecipeExecutor ::class .java)
3740 }
@@ -40,11 +43,11 @@ class ZipRecipeExecutor(
4043 executor : RecipeExecutor
4144 ): ProjectTemplateRecipeResult {
4245
43- log.debug( " executor called!! " )
46+ info( " Starting project creation for $basePath " )
4447
4548 val projectDir = data.projectDir
4649 if (projectDir.exists()) {
47- return ProjectTemplateRecipeResultImpl (data)
50+ return ProjectTemplateRecipeResultImpl (data, hasErrorsWarnings )
4851 }
4952
5053 val projectRoot = projectDir.canonicalFile
@@ -56,77 +59,118 @@ class ZipRecipeExecutor(
5659
5760 zipProvider().use { zip ->
5861
59- val customSyntax = Syntax .Builder ()
60- .setPrintOpenDelimiter(DELIM_PRINT_OPEN )
61- .setPrintCloseDelimiter(DELIM_PRINT_CLOSE )
62- .setExecuteOpenDelimiter(DELIM_EXECUTE_OPEN )
63- .setExecuteCloseDelimiter(DELIM_EXECUTE_CLOSE )
64- .setCommentOpenDelimiter(DELIM_COMMENT_OPEN )
65- .setCommentCloseDelimiter(DELIM_COMMENT_CLOSE )
66- .build()
67-
68- val pebbleEngine = PebbleEngine .Builder ()
69- .loader(StringLoader ())
70- .syntax(customSyntax)
71- .build()
72-
73- val (identifiers, warnings) = metaJson.pebbleParams(data, defModule, params)
74- log.debug(" identifiers warnings: ${warnings.joinToString(System .lineSeparator())} " )
75-
76- val packageName =
77- resolveString(metaJson.parameters?.required?.packageName?.identifier, KEY_PACKAGE_NAME )
78-
79- for (entry in zip.entries()) {
80- if (! entry.name.startsWith(" $basePath /" )) continue
81- if (entry.name == " $basePath /" ) continue
82- if (entry.name.startsWith(" $basePath /$META_FOLDER /" )) continue
83-
84- if ((metaJson.parameters?.optional?.language != null ) &&
85- (data.language != null ) &&
86- shouldSkipFile(
87- entry.name.removeSuffix(TEMPLATE_EXTENSION ),
88- safeLanguageName(data.language)
89- )
90- ) continue
91-
92- val normalized = filterAndNormalizeZipEntry(entry.name, flags) ? : continue
93-
94- val relativePath = normalized.removePrefix(" $basePath /" )
95- .replace(packageName.value, defModule.packageName.replace(" ." , " /" ))
96-
97- val outFile = File (projectDir, relativePath.removeSuffix(TEMPLATE_EXTENSION )).canonicalFile
98-
99- if (! outFile.toPath().startsWith(projectRoot.toPath())) {
100- log.warn(" Skipping suspicious ZIP entry outside project dir: {}" , entry.name)
101- continue
62+ val customSyntax = Syntax .Builder ()
63+ .setPrintOpenDelimiter(DELIM_PRINT_OPEN )
64+ .setPrintCloseDelimiter(DELIM_PRINT_CLOSE )
65+ .setExecuteOpenDelimiter(DELIM_EXECUTE_OPEN )
66+ .setExecuteCloseDelimiter(DELIM_EXECUTE_CLOSE )
67+ .setCommentOpenDelimiter(DELIM_COMMENT_OPEN )
68+ .setCommentCloseDelimiter(DELIM_COMMENT_CLOSE )
69+ .build()
70+
71+ val pebbleEngine = PebbleEngine .Builder ()
72+ .loader(StringLoader ())
73+ .syntax(customSyntax)
74+ .build()
75+
76+ val (identifiers, warnings) = metaJson.pebbleParams(data, defModule, params)
77+ if (warnings.isNotEmpty()) {
78+ warn(" Identifier warnings: ${warnings.joinToString(System .lineSeparator())} " )
10279 }
10380
104- if (entry.isDirectory) {
105- outFile.mkdirs()
106- } else {
107- outFile.parentFile?.mkdirs()
108-
109- if (entry.name.endsWith(TEMPLATE_EXTENSION )) {
110- log.debug(" template processing ${entry.name} " )
111- val content = zip.getInputStream(entry).bufferedReader().use { it.readText() }
112- val template = pebbleEngine.getTemplate(content)
113- val writer = StringWriter ()
114- template.evaluate(writer, identifiers)
115- outFile.writeText(writer.toString(), Charsets .UTF_8 )
116- } else {
117- zip.getInputStream(entry).use { input ->
118- outFile.outputStream().use { output ->
119- input.copyTo(output)
81+ val packageName =
82+ resolveString(metaJson.parameters?.required?.packageName?.identifier, KEY_PACKAGE_NAME )
83+
84+ for (entry in zip.entries()) {
85+ if (! entry.name.startsWith(" $basePath /" )) continue
86+ if (entry.name == " $basePath /" ) continue
87+ if (entry.name.startsWith(" $basePath /$META_FOLDER /" )) continue
88+
89+ if ((metaJson.parameters?.optional?.language != null ) &&
90+ (data.language != null ) &&
91+ shouldSkipFile(
92+ entry.name.removeSuffix(TEMPLATE_EXTENSION ),
93+ safeLanguageName(data.language)
94+ )
95+ ) continue
96+
97+ val normalized = filterAndNormalizeZipEntry(entry.name, flags) ? : continue
98+
99+ val relativePath = normalized.removePrefix(" $basePath /" )
100+ .replace(packageName.value, defModule.packageName.replace(" ." , " /" ))
101+
102+ val outFile = File (projectDir, relativePath.removeSuffix(TEMPLATE_EXTENSION )).canonicalFile
103+
104+ if (! outFile.toPath().startsWith(projectRoot.toPath())) {
105+ warn(" Skipping suspicious template entry outside project dir: ${entry.name} " )
106+ continue
107+ }
108+
109+ if (entry.isDirectory) {
110+ outFile.mkdirs()
111+ } else {
112+ try {
113+ outFile.parentFile?.mkdirs()
114+
115+ if (entry.name.endsWith(TEMPLATE_EXTENSION )) {
116+ info(" Processing template ${entry.name} " )
117+ val content = try {
118+ zip.getInputStream(entry).bufferedReader().use { it.readText() }
119+ } catch (e: Exception ) {
120+ throw e.wrap(" Failed to read template ${entry.name} " )
121+ }
122+
123+ val template = try {
124+ pebbleEngine.getTemplate(content)
125+ } catch (e: PebbleException ) {
126+ throw e.wrap(
127+ " Pebble parse error in ${entry.name} at line ${e.lineNumber} : ${e.message} "
128+ )
129+ } catch (e: Exception ) {
130+ throw e.wrap(" Unexpected Pebble parse error in ${entry.name} " )
131+ }
132+
133+ val writer = StringWriter ()
134+ val rendered = try {
135+ template.evaluate(writer, identifiers)
136+ } catch (e: PebbleException ) {
137+ error(
138+ " Pebble evaluation error in ${entry.name} at line ${e.lineNumber} : ${e.message} " , e
139+ )
140+ null
141+ } catch (e: Exception ) {
142+ error(" Unexpected Pebble evaluation error in ${entry.name} " , e)
143+ null
144+ }
145+ if (rendered == null ) continue
146+
147+ try {
148+ outFile.writeText(writer.toString(), Charsets .UTF_8 )
149+ } catch (e: Exception ) {
150+ error(" Failed writing output file: ${outFile.absolutePath} " , e)
151+ }
152+
153+ } else {
154+ try {
155+ zip.getInputStream(entry).use { input ->
156+ outFile.outputStream().use { output ->
157+ input.copyTo(output)
158+ }
159+ }
160+ } catch (e: Exception ) {
161+ error(" Failed copying binary entry: ${entry.name} " , e)
120162 }
121163 }
164+ } catch (e: Exception ) {
165+ error(" Failed to process template entry: ${entry.name} " , e)
122166 }
123167 }
124168 }
125169 }
126170
127171 keystore(executor)
128172
129- return ProjectTemplateRecipeResultImpl (data)
173+ return ProjectTemplateRecipeResultImpl (data, hasErrorsWarnings )
130174 }
131175
132176 private fun keystore (executor : RecipeExecutor ) {
@@ -266,4 +310,20 @@ class ZipRecipeExecutor(
266310 return normalizedParts.joinToString(File .separator)
267311 }
268312
313+ private fun warn (msg : String ) {
314+ hasErrorsWarnings = true
315+ log.warn(msg)
316+ }
317+
318+ private fun info (msg : String ) {
319+ log.info(msg)
320+ }
321+
322+ private fun error (msg : String , e : Exception ) {
323+ hasErrorsWarnings = true
324+ log.error(msg, e)
325+ }
326+
327+ private fun Exception.wrap (msg : String ): RuntimeException =
328+ RuntimeException (msg, this )
269329}
0 commit comments