diff --git a/config.jsonc b/config.jsonc new file mode 100644 index 0000000..d322ff2 --- /dev/null +++ b/config.jsonc @@ -0,0 +1,40 @@ +{ + // OSM Maker Configuration File + // This file demonstrates JSONC (JSON with Comments) support + + "osmData": { + "useLocalExtract": false, // Set to true to use local OSM file + "localFilePath": "virginia.osm.pbf", + + /* Bounding box configuration + * Defines the geographic area to process + */ + "boundingBox": { + "south": 37.115, // Southern latitude boundary + "west": -76.396, // Western longitude boundary + "north": 37.139, // Northern latitude boundary + "east": -76.345, // Eastern longitude boundary + "description": "Poquoson, VA" // Human-readable description + }, + + "overpassTimeout": 25 // Timeout for Overpass API queries in seconds + }, + + // Projection settings for coordinate transformation + "projection": { + "origin": { + "latitude": 37.120907, // Center point latitude + "longitude": -76.333694 // Center point longitude + } + }, + + /* Output configuration + * Controls how the final 3D model is generated and handled + */ + "output": { + "fileName": "municipality.glb", // Output file name + "autoOpen": true // Whether to automatically open the generated file + } + + // End of configuration +} \ No newline at end of file diff --git a/src/commonMain/kotlin/Config.kt b/src/commonMain/kotlin/Config.kt index 033cdd7..4680d11 100644 --- a/src/commonMain/kotlin/Config.kt +++ b/src/commonMain/kotlin/Config.kt @@ -1,6 +1,7 @@ package org.example import kotlinx.serialization.Serializable +import kotlinx.serialization.json.Json @Serializable data class Config( @@ -41,4 +42,87 @@ data class OriginConfig( data class OutputConfig( val fileName: String, val autoOpen: Boolean -) \ No newline at end of file +) + +/** + * Parses JSONC (JSON with Comments) content by removing comments and parsing as regular JSON + */ +fun parseJsonc(jsoncContent: String): Config { + val cleanedJson = removeJsoncComments(jsoncContent) + return Json.decodeFromString(cleanedJson) +} + +/** + * Removes both single-line (//) and multi-line (/* */) comments from JSONC content + */ +private fun removeJsoncComments(content: String): String { + val result = StringBuilder() + var i = 0 + var inString = false + var escaped = false + + while (i < content.length) { + val char = content[i] + + when { + // Handle escape sequences in strings + escaped -> { + result.append(char) + escaped = false + i++ + } + // Handle string boundaries + char == '"' && !escaped -> { + inString = !inString + result.append(char) + i++ + } + // Handle escape character + char == '\\' && inString -> { + escaped = true + result.append(char) + i++ + } + // Skip comments when not in string + !inString && char == '/' && i + 1 < content.length -> { + when (content[i + 1]) { + // Single-line comment + '/' -> { + // Skip until end of line + i += 2 + while (i < content.length && content[i] != '\n') { + i++ + } + // Keep the newline for proper formatting + if (i < content.length) { + result.append('\n') + i++ + } + } + // Multi-line comment + '*' -> { + // Skip until */ + i += 2 + while (i + 1 < content.length && !(content[i] == '*' && content[i + 1] == '/')) { + i++ + } + if (i + 1 < content.length) { + i += 2 // Skip the closing */ + } + } + else -> { + result.append(char) + i++ + } + } + } + // Regular character + else -> { + result.append(char) + i++ + } + } + } + + return result.toString() +} diff --git a/src/jvmTest/kotlin/ConfigTest.kt b/src/jvmTest/kotlin/ConfigTest.kt index 50e4327..728f5b9 100644 --- a/src/jvmTest/kotlin/ConfigTest.kt +++ b/src/jvmTest/kotlin/ConfigTest.kt @@ -4,9 +4,10 @@ import kotlinx.serialization.json.Json import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull +import java.io.File class ConfigTest { - + @Test fun testConfigDeserialization() { val jsonString = """ @@ -35,9 +36,9 @@ class ConfigTest { } } """.trimIndent() - + val config = Json.decodeFromString(jsonString) - + assertNotNull(config) assertEquals(false, config.osmData.useLocalExtract) assertEquals("virginia.osm.pbf", config.osmData.localFilePath) @@ -52,4 +53,155 @@ class ConfigTest { assertEquals("municipality.glb", config.output.fileName) assertEquals(true, config.output.autoOpen) } -} \ No newline at end of file + + @Test + fun testJsoncWithSingleLineComments() { + val jsoncString = """ + { + // This is a comment about OSM data configuration + "osmData": { + "useLocalExtract": false, // Use remote data instead + "localFilePath": "virginia.osm.pbf", + "boundingBox": { + "south": 37.115, + "west": -76.396, + "north": 37.139, + "east": -76.345, + "description": "Poquoson, VA" // A small city in Virginia + }, + "overpassTimeout": 25 // Timeout in seconds + }, + "projection": { + "origin": { + "latitude": 37.120907, // Center latitude + "longitude": -76.333694 // Center longitude + } + }, + "output": { + "fileName": "municipality.glb", // Output file name + "autoOpen": true // Automatically open the file + } + } + """.trimIndent() + + val config = parseJsonc(jsoncString) + + assertNotNull(config) + assertEquals(false, config.osmData.useLocalExtract) + assertEquals("virginia.osm.pbf", config.osmData.localFilePath) + assertEquals("Poquoson, VA", config.osmData.boundingBox.description) + assertEquals(25, config.osmData.overpassTimeout) + assertEquals("municipality.glb", config.output.fileName) + assertEquals(true, config.output.autoOpen) + } + + @Test + fun testJsoncWithMultiLineComments() { + val jsoncString = """ + { + /* + * OSM Data Configuration + * This section configures how OSM data is obtained + */ + "osmData": { + "useLocalExtract": false, + "localFilePath": "virginia.osm.pbf", + "boundingBox": { + "south": 37.115, + "west": -76.396, + "north": 37.139, + "east": -76.345, + "description": "Poquoson, VA" + }, + "overpassTimeout": 25 + }, + /* Projection settings */ + "projection": { + "origin": { + "latitude": 37.120907, + "longitude": -76.333694 + } + }, + /* + * Output configuration + * Controls how the final file is generated + */ + "output": { + "fileName": "municipality.glb", + "autoOpen": true + } + } + """.trimIndent() + + val config = parseJsonc(jsoncString) + + assertNotNull(config) + assertEquals(false, config.osmData.useLocalExtract) + assertEquals("Poquoson, VA", config.osmData.boundingBox.description) + assertEquals("municipality.glb", config.output.fileName) + } + + @Test + fun testJsoncWithMixedComments() { + val jsoncString = """ + { + // Single line comment at the top + "osmData": { + /* Multi-line comment + about local extract */ + "useLocalExtract": false, // Inline comment + "localFilePath": "virginia.osm.pbf", + "boundingBox": { + "south": 37.115, // Southern boundary + "west": -76.396, + "north": 37.139, + "east": -76.345, + "description": "Poquoson, VA" + }, + "overpassTimeout": 25 + }, + "projection": { + "origin": { + "latitude": 37.120907, + "longitude": -76.333694 + } + }, + "output": { + "fileName": "municipality.glb", + "autoOpen": true + } + // Final comment + } + """.trimIndent() + + val config = parseJsonc(jsoncString) + + assertNotNull(config) + assertEquals(false, config.osmData.useLocalExtract) + assertEquals("Poquoson, VA", config.osmData.boundingBox.description) + } + + @Test + fun testLoadActualJsoncFile() { + val configFile = File("config.jsonc") + if (!configFile.exists()) { + println("Skipping test - config.jsonc file not found") + return + } + + val configText = configFile.readText() + val config = parseJsonc(configText) + + assertNotNull(config) + assertEquals(false, config.osmData.useLocalExtract) + assertEquals("virginia.osm.pbf", config.osmData.localFilePath) + assertEquals("Poquoson, VA", config.osmData.boundingBox.description) + assertEquals(25, config.osmData.overpassTimeout) + assertEquals(37.120907, config.projection.origin.latitude) + assertEquals(-76.333694, config.projection.origin.longitude) + assertEquals("municipality.glb", config.output.fileName) + assertEquals(true, config.output.autoOpen) + + println("āœ… Successfully loaded and parsed config.jsonc file with comments!") + } +} diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 92c36d1..6ea8c8a 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -10,22 +10,39 @@ fun main() { LOAD AND DEMONSTRATE JSON CONFIGURATION ---------------------------------------------------------------- */ - val configFile = File("config.json") - if (!configFile.exists()) { - println("Error: config.json not found in current directory") - println("Please ensure config.json exists in the working directory") - return + // Try to find configuration file (prefer .jsonc, fallback to .json) + val configFile = when { + File("config.jsonc").exists() -> File("config.jsonc") + File("config.json").exists() -> File("config.json") + else -> { + println("Error: No configuration file found") + println("Please ensure either config.jsonc or config.json exists in the working directory") + return + } } val config = try { val configText = configFile.readText() - Json.decodeFromString(configText) + when (configFile.extension.lowercase()) { + "jsonc" -> { + println("šŸ“„ Loading JSONC configuration from ${configFile.name}") + parseJsonc(configText) + } + "json" -> { + println("šŸ“„ Loading JSON configuration from ${configFile.name}") + Json.decodeFromString(configText) + } + else -> { + println("Error: Unsupported configuration file format: ${configFile.extension}") + return + } + } } catch (e: Exception) { println("Error reading configuration: ${e.message}") return } - println("\nāœ… Configuration loaded successfully from config.json!") + println("\nāœ… Configuration loaded successfully from ${configFile.name}!") println("šŸ“ Area: ${config.osmData.boundingBox.description}") println("šŸ“‚ Use local extract: ${config.osmData.useLocalExtract}") println("šŸ“„ Local file path: ${config.osmData.localFilePath}") @@ -67,6 +84,6 @@ fun main() { println("5. Auto-open is disabled, file would remain closed") } - println("\n✨ JSON configuration reading implementation complete!") - println("šŸŽ‰ The app now successfully reads configuration from JSON instead of using hardcoded values.") + println("\n✨ JSON/JSONC configuration reading implementation complete!") + println("šŸŽ‰ The app now successfully reads configuration from JSON and JSONC files with comment support!") }