core: коллекция — каталог с markdown напрямую; исправлен тройной обход
This commit is contained in:
@@ -2,30 +2,31 @@ package memo.core
|
||||
|
||||
import java.io.File
|
||||
|
||||
fun isCollection(dir: File): Boolean {
|
||||
if (!dir.isDirectory) return false
|
||||
return dir.listFiles()?.any { it.isFile && it.extension == "md" } == true
|
||||
}
|
||||
|
||||
fun resolveCollection(path: File): File {
|
||||
return if (path.name == ".memo") path.parentFile ?: path else path
|
||||
if (path.isFile) return path.parentFile ?: path
|
||||
if (isCollection(path)) return path
|
||||
val descendants = findCollections(path)
|
||||
if (descendants.size == 1) return descendants[0]
|
||||
return path
|
||||
}
|
||||
|
||||
fun findCollections(root: File): List<File> {
|
||||
if (!root.isDirectory) return emptyList()
|
||||
val candidates = LinkedHashSet<File>()
|
||||
candidates.add(root)
|
||||
val q = ArrayDeque<Pair<File, Int>>()
|
||||
q.addLast(root to 0)
|
||||
while (q.isNotEmpty()) {
|
||||
val (d, depth) = q.removeFirst()
|
||||
if (depth >= 2) continue
|
||||
val children = d.listFiles() ?: continue
|
||||
val result = LinkedHashSet<File>()
|
||||
fun walk(d: File) {
|
||||
if (isCollection(d)) result.add(d)
|
||||
val children = d.listFiles() ?: return
|
||||
for (c in children) {
|
||||
if (c.isDirectory && !c.name.startsWith(".")) {
|
||||
candidates.add(c)
|
||||
q.addLast(c to depth + 1)
|
||||
walk(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return candidates.filter { d ->
|
||||
d.walkTopDown()
|
||||
.maxDepth(8)
|
||||
.any { it.isFile && it.extension == "md" }
|
||||
}
|
||||
walk(root)
|
||||
return result.sortedBy { it.absolutePath }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package memo.core
|
||||
|
||||
import java.io.File
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class CollectionsTest {
|
||||
|
||||
private fun newRoot(): File {
|
||||
val root = File.createTempFile("memo-collections-", "")
|
||||
assertTrue(root.delete(), "temp cleanup")
|
||||
assertTrue(root.mkdirs(), "temp mkdir")
|
||||
root.deleteOnExit()
|
||||
return root
|
||||
}
|
||||
|
||||
private fun touch(parent: File, vararg rel: String): File {
|
||||
var cur = parent
|
||||
for (seg in rel.dropLast(1)) {
|
||||
cur = File(cur, seg)
|
||||
cur.mkdirs()
|
||||
}
|
||||
val f = File(cur, rel.last())
|
||||
f.writeText("# ${rel.last()}\n")
|
||||
f.deleteOnExit()
|
||||
return f
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findCollectionsReturnsOnlyDirsWithDirectMarkdown() {
|
||||
val root = newRoot()
|
||||
touch(root, "a.md")
|
||||
touch(root, "sub", "b.md")
|
||||
touch(root, "sub", "deep", "c.md")
|
||||
File(root, "empty").mkdirs()
|
||||
touch(root, "nested", "only", "deep", "d.md")
|
||||
|
||||
val got = findCollections(root).map { it.absolutePath }.toSet()
|
||||
val want = setOf(
|
||||
root.absolutePath,
|
||||
File(root, "sub").absolutePath,
|
||||
File(root, "sub/deep").absolutePath,
|
||||
File(root, "nested/only/deep").absolutePath,
|
||||
)
|
||||
assertEquals(want, got)
|
||||
assertTrue(File(root, "empty").absolutePath !in got, "empty must not be a collection")
|
||||
assertTrue(File(root, "nested").absolutePath !in got, "nested must not be a collection")
|
||||
assertTrue(File(root, "nested/only").absolutePath !in got, "nested/only must not be a collection")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parentDirWithoutDirectMarkdownIsNotCollection() {
|
||||
val root = newRoot()
|
||||
touch(root, "top", "inner", "x.md")
|
||||
|
||||
val got = findCollections(root).map { it.absolutePath }.toSet()
|
||||
assertEquals(setOf(File(root, "top/inner").absolutePath), got)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveCollectionRaisesToNearestWithMarkdown() {
|
||||
val root = newRoot()
|
||||
val note = touch(root, "top", "inner", "x.md")
|
||||
|
||||
val fileResult = resolveCollection(note)
|
||||
assertEquals(File(root, "top/inner").absolutePath, fileResult.absolutePath)
|
||||
|
||||
val dirResult = resolveCollection(File(root, "top"))
|
||||
assertEquals(File(root, "top/inner").absolutePath, dirResult.absolutePath)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hiddenDirsAreSkipped() {
|
||||
val root = newRoot()
|
||||
touch(root, ".hidden", "a.md")
|
||||
touch(root, "vis", "b.md")
|
||||
File(root, ".memo").mkdirs()
|
||||
File(File(root, ".memo"), "index.db").writeText("")
|
||||
|
||||
val got = findCollections(root).map { it.absolutePath }.toSet()
|
||||
assertEquals(setOf(File(root, "vis").absolutePath), got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user