Skip to content

Commit ca5ede6

Browse files
committed
* Better cache design - continuation
1 parent 6a007d5 commit ca5ede6

File tree

9 files changed

+109
-75
lines changed

9 files changed

+109
-75
lines changed

src/main/kotlin/kscript/app/KscriptHandler.kt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ import kscript.app.creator.*
66
import kscript.app.model.Config
77
import kscript.app.model.ScriptType
88
import kscript.app.parser.Parser
9-
import kscript.app.resolver.CommandResolver
10-
import kscript.app.resolver.DependencyResolver
11-
import kscript.app.resolver.ScriptResolver
12-
import kscript.app.resolver.SectionResolver
9+
import kscript.app.resolver.*
1310
import kscript.app.util.Executor
1411
import kscript.app.util.Logger
1512
import kscript.app.util.Logger.infoMsg
@@ -43,8 +40,9 @@ class KscriptHandler(private val config: Config, private val docopt: DocOptWrapp
4340
add(config.customPreamble)
4441
}
4542

46-
val sectionResolver = SectionResolver(Parser(), appDir.cache, config)
47-
val scriptResolver = ScriptResolver(sectionResolver, appDir.cache, config.kotlinOptsEnvVariable)
43+
val contentResolver = ContentResolver(appDir.cache)
44+
val sectionResolver = SectionResolver(Parser(), contentResolver, config)
45+
val scriptResolver = ScriptResolver(sectionResolver, contentResolver, config.kotlinOptsEnvVariable)
4846

4947
if (docopt.getBoolean("add-bootstrap-header")) {
5048
val script = scriptResolver.resolve(docopt.getString("script"), maxResolutionLevel = 0)
@@ -59,7 +57,7 @@ class KscriptHandler(private val config: Config, private val docopt: DocOptWrapp
5957
// Create temporary dev environment
6058
if (docopt.getBoolean("idea")) {
6159
val path = appDir.cache.getOrCreateIdeaProject(script.digest) { basePath ->
62-
val uriLocalPathProvider = { uri: URI -> appDir.cache.getOrCreateUriItem(uri).path }
60+
val uriLocalPathProvider = { uri: URI -> contentResolver.resolve(uri).localPath }
6361
IdeaProjectCreator().create(basePath, script, userArgs, uriLocalPathProvider)
6462
}
6563

src/main/kotlin/kscript/app/appdir/Cache.kt

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,17 @@ package kscript.app.appdir
22

33
import kscript.app.creator.JarArtifact
44
import kscript.app.model.ScriptType
5-
import kscript.app.util.Logger.devMsg
6-
import kscript.app.util.ScriptUtils
5+
import kscript.app.model.Content
76
import org.apache.commons.codec.digest.DigestUtils
87
import java.net.URI
8+
import java.net.URL
99
import java.nio.file.Path
1010
import java.nio.file.Paths
1111
import kotlin.io.path.createDirectories
1212
import kotlin.io.path.exists
1313
import kotlin.io.path.readText
1414
import kotlin.io.path.writeText
1515

16-
data class UriItem(
17-
val content: String,
18-
val scriptType: ScriptType,
19-
val fileName: String,
20-
val uri: URI, //Real one from Web, not the cached file
21-
val contextUri: URI, //Real one from Web, not the cached file
22-
val path: Path //Path to local file
23-
)
24-
2516
class Cache(private val path: Path) {
2617
fun getOrCreateIdeaProject(digest: String, creator: (Path) -> Path): Path {
2718
return directoryCache(path.resolve("idea_$digest"), creator)
@@ -46,47 +37,33 @@ class Cache(private val path: Path) {
4637
}
4738
}
4839

49-
fun getOrCreateUriItem(uri: URI): UriItem {
50-
val digest = DigestUtils.md5Hex(uri.toString())
51-
52-
if (uri.scheme == "file") {
53-
val content = uri.toURL().readText()
54-
val scriptType = ScriptUtils.resolveScriptType(uri) ?: ScriptUtils.resolveScriptType(content)
55-
val fileName = ScriptUtils.extractFileName(uri)
56-
val contextUri = uri.resolve(".")
57-
return UriItem(content, scriptType, fileName, uri, contextUri, Paths.get(uri))
58-
}
40+
fun getOrCreateUriItem(url: URL, creator: (URL, Path) -> Content): Content {
41+
val digest = DigestUtils.md5Hex(url.toString())
5942

60-
val directory = path.resolve("uri_$digest").createDirectories()
61-
val descriptorFile = directory.resolve("uri.descriptor")
62-
val contentFile = directory.resolve("uri.content")
43+
val directory = path.resolve("url_$digest")
44+
val descriptorFile = directory.resolve("url.descriptor")
45+
val contentFile = directory.resolve("url.content")
6346

6447
if (descriptorFile.exists() && contentFile.exists()) {
6548
//Cache hit
6649
val descriptor = descriptorFile.readText().lines()
67-
68-
devMsg("Descriptor: $descriptor")
69-
7050
val scriptType = ScriptType.valueOf(descriptor[0])
7151
val fileName = descriptor[1]
7252
val cachedUri = URI.create(descriptor[2])
7353
val contextUri = URI.create(descriptor[3])
7454
val content = contentFile.readText()
7555

76-
return UriItem(content, scriptType, fileName, cachedUri, contextUri, contentFile)
56+
return Content(content, scriptType, fileName, cachedUri, contextUri, contentFile)
7757
}
7858

79-
//Otherwise, resolve web file and cache it...
80-
val resolvedUri = ScriptUtils.resolveRedirects(uri.toURL()).toURI()
81-
val content = resolvedUri.toURL().readText()
82-
val scriptType = ScriptUtils.resolveScriptType(resolvedUri) ?: ScriptUtils.resolveScriptType(content)
83-
val fileName = ScriptUtils.extractFileName(resolvedUri)
84-
val contextUri = resolvedUri.resolve(".")
59+
//Cache miss
60+
val content = creator(url, contentFile)
8561

86-
descriptorFile.writeText("$scriptType\n$fileName\n$resolvedUri\n$contextUri")
87-
contentFile.writeText(content)
62+
directory.createDirectories()
63+
descriptorFile.writeText("${content.scriptType}\n${content.fileName}\n${content.uri}\n${content.contextUri}")
64+
contentFile.writeText(content.text)
8865

89-
return UriItem(content, scriptType, fileName, resolvedUri, contextUri, contentFile)
66+
return content
9067
}
9168

9269
private fun directoryCache(path: Path, creator: (Path) -> Path): Path {
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package kscript.app.model
2+
3+
import java.net.URI
4+
import java.nio.file.Path
5+
6+
data class Content(
7+
val text: String,
8+
val scriptType: ScriptType,
9+
val fileName: String,
10+
val uri: URI, //Real one from Web, not the cached file
11+
val contextUri: URI, //Real one from Web, not the cached file
12+
val localPath: Path, //Local file path
13+
)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package kscript.app.resolver
2+
3+
import kscript.app.appdir.Cache
4+
import kscript.app.model.Content
5+
import kscript.app.util.ScriptUtils
6+
import kscript.app.util.ScriptUtils.isUrl
7+
import java.net.URI
8+
import java.net.URL
9+
import java.nio.file.Path
10+
import java.nio.file.Paths
11+
import kotlin.io.path.readText
12+
13+
class ContentResolver(private val cache: Cache) {
14+
15+
fun resolve(filePath: Path): Content {
16+
val uri = filePath.toUri()
17+
val content = filePath.readText()
18+
val scriptType = ScriptUtils.resolveScriptType(uri) ?: ScriptUtils.resolveScriptType(content)
19+
val fileName = ScriptUtils.extractFileName(uri)
20+
val contextUri = uri.resolve(".")
21+
return Content(content, scriptType, fileName, uri, contextUri, filePath)
22+
}
23+
24+
fun resolve(url: URL): Content {
25+
return cache.getOrCreateUriItem(url) { sourceUrl, contentFile ->
26+
val resolvedUri = ScriptUtils.resolveRedirects(sourceUrl).toURI()
27+
val content = resolvedUri.toURL().readText()
28+
val scriptType = ScriptUtils.resolveScriptType(resolvedUri) ?: ScriptUtils.resolveScriptType(content)
29+
val fileName = ScriptUtils.extractFileName(resolvedUri)
30+
val contextUri = resolvedUri.resolve(".")
31+
32+
Content(content, scriptType, fileName, resolvedUri, contextUri, contentFile)
33+
}
34+
}
35+
36+
fun resolve(uri: URI): Content {
37+
return if (isUrl(uri)) {
38+
resolve(uri.toURL())
39+
} else {
40+
resolve(Paths.get(uri))
41+
}
42+
}
43+
}

src/main/kotlin/kscript/app/resolver/ScriptResolver.kt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package kscript.app.resolver
22

3-
import kscript.app.appdir.Cache
43
import kscript.app.model.*
54
import kscript.app.parser.LineParser.extractValues
65
import kscript.app.util.ScriptUtils
@@ -11,7 +10,7 @@ import java.net.URL
1110

1211
class ScriptResolver(
1312
private val sectionResolver: SectionResolver,
14-
private val cache: Cache,
13+
private val contentResolver: ContentResolver,
1514
private val kotlinOptsEnvVariable: String = ""
1615
) {
1716
private val kotlinExtensions = listOf("kts", "kt")
@@ -44,15 +43,15 @@ class ScriptResolver(
4443

4544
//Is it a URL?
4645
if (ScriptUtils.isUrl(string)) {
47-
val uriItem = cache.getOrCreateUriItem(URL(string).toURI())
48-
val scriptText = ScriptUtils.prependPreambles(preambles, uriItem.content)
46+
val content = contentResolver.resolve(URL(string))
47+
val scriptText = ScriptUtils.prependPreambles(preambles, content.text)
4948

5049
return createScript(
5150
ScriptSource.HTTP,
52-
uriItem.scriptType,
53-
uriItem.uri,
54-
uriItem.contextUri,
55-
uriItem.fileName,
51+
content.scriptType,
52+
content.uri,
53+
content.contextUri,
54+
content.fileName,
5655
scriptText,
5756
false,
5857
maxResolutionLevel
@@ -63,15 +62,15 @@ class ScriptResolver(
6362
if (file.canRead()) {
6463
if (kotlinExtensions.contains(file.extension)) {
6564
//Regular file
66-
val uriItem = cache.getOrCreateUriItem(file.toURI())
67-
val scriptText = ScriptUtils.prependPreambles(preambles, uriItem.content)
65+
val content = contentResolver.resolve(file.toPath())
66+
val scriptText = ScriptUtils.prependPreambles(preambles, content.text)
6867

6968
return createScript(
7069
ScriptSource.FILE,
71-
uriItem.scriptType,
72-
uriItem.uri,
73-
uriItem.contextUri,
74-
uriItem.fileName,
70+
content.scriptType,
71+
content.uri,
72+
content.contextUri,
73+
content.fileName,
7574
scriptText,
7675
true,
7776
maxResolutionLevel
@@ -108,7 +107,8 @@ class ScriptResolver(
108107
val scriptType = ScriptUtils.resolveScriptType(scriptText)
109108

110109
return createScript(
111-
ScriptSource.PARAMETER, scriptType,
110+
ScriptSource.PARAMETER,
111+
scriptType,
112112
null,
113113
File(".").toURI(),
114114
scripletName + scriptType.extension,

src/main/kotlin/kscript/app/resolver/SectionResolver.kt

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package kscript.app.resolver
22

3-
import kscript.app.appdir.Cache
43
import kscript.app.model.*
54
import kscript.app.parser.Parser
65
import kscript.app.util.ScriptUtils
76
import java.io.File
87
import java.net.URI
98
import java.nio.file.Path
109

11-
class SectionResolver(private val parser: Parser, private val cache: Cache, private val config: Config) {
10+
class SectionResolver(private val parser: Parser, private val contentResolver: ContentResolver, private val config: Config) {
1211
fun resolve(
1312
scriptText: String,
1413
includeContext: URI,
@@ -67,11 +66,11 @@ class SectionResolver(private val parser: Parser, private val cache: Cache, priv
6766
throw IllegalStateException("References to local files from remote scripts are disallowed.")
6867
}
6968

70-
val uriItem = cache.getOrCreateUriItem(uri)
69+
val content = contentResolver.resolve(uri)
7170

7271
val newSections = resolve(
73-
uriItem.content,
74-
uriItem.contextUri,
72+
content.text,
73+
content.contextUri,
7574
allowLocalReferences && scriptSource == ScriptSource.FILE,
7675
currentLevel + 1,
7776
maxResolutionLevel,
@@ -81,9 +80,9 @@ class SectionResolver(private val parser: Parser, private val cache: Cache, priv
8180
val scriptNode = ScriptNode(
8281
currentLevel + 1,
8382
scriptSource,
84-
uriItem.scriptType,
83+
content.scriptType,
8584
uri,
86-
uriItem.contextUri,
85+
content.contextUri,
8786
ScriptUtils.extractFileName(uri),
8887
newSections
8988
)

src/main/kotlin/kscript/app/util/ScriptUtils.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ object ScriptUtils {
2626
return normalizedString.startsWith("http://") || normalizedString.startsWith("https://")
2727
}
2828

29+
fun isUrl(uri: URI) = uri.scheme.equals("http") || uri.scheme.equals("https")
30+
2931
fun isRegularFile(uri: URI) = uri.scheme.startsWith("file")
3032

3133
fun String.dropExtension(): String {
3234
val name = extractFileName(this)
3335

3436
if (name.indexOf(".") > 0) {
35-
return name.substring(0, name.lastIndexOf("."));
37+
return name.substring(0, name.lastIndexOf("."))
3638
}
3739

3840
return name

src/test/kotlin/kscript/app/resolver/CommandResolverTest.kt

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
package kscript.app.resolver
22

3+
import io.mockk.mockk
34
import kscript.app.appdir.Cache
45
import kscript.app.creator.JarArtifact
56
import kscript.app.model.Config
67
import kscript.app.parser.Parser
7-
88
import org.junit.jupiter.api.Test
99
import java.nio.file.Paths
10-
import kotlin.io.path.createDirectories
1110

1211
internal class CommandResolverTest {
1312
private val testHome = Paths.get("build/tmp/script_resolver_test")
1413
private val config = Config.builder().apply { homeDir = testHome.resolve("home") }.build()
15-
private val cache = Cache(testHome.resolve("cache").createDirectories())
16-
private val sectionResolver = SectionResolver(Parser(), cache, config)
17-
private val scriptResolver = ScriptResolver(sectionResolver, cache)
18-
private val commandResolver = CommandResolver(Config.builder().build(), scriptResolver.resolve("println(\"Kotlin rocks\")"))
14+
private val cache = mockk<Cache>()
15+
private val contentResolver = ContentResolver(cache)
16+
private val sectionResolver = SectionResolver(Parser(), contentResolver, config)
17+
private val scriptResolver = ScriptResolver(sectionResolver, contentResolver)
18+
private val commandResolver =
19+
CommandResolver(Config.builder().build(), scriptResolver.resolve("println(\"Kotlin rocks\")"))
1920

2021
@Test
2122
fun executeKotlin() {

src/test/kotlin/kscript/app/resolver/ScriptResolverTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ class ScriptResolverTest {
1818
private val testHome = Paths.get("build/tmp/script_resolver_test")
1919
private val config = Config.builder().apply { homeDir = testHome.resolve("home") }.build()
2020
private val cache = Cache(testHome.resolve("cache").createDirectories())
21-
private val sectionResolver = SectionResolver(Parser(), cache, config)
22-
private val scriptResolver = ScriptResolver(sectionResolver, cache)
21+
private val contentResolver = ContentResolver(cache)
22+
private val sectionResolver = SectionResolver(Parser(), contentResolver, config)
23+
private val scriptResolver = ScriptResolver(sectionResolver, contentResolver)
2324

2425
private val defaultPackageName = PackageName("kscript.scriplet")
2526

0 commit comments

Comments
 (0)