Skip to content

Commit 8a59aa3

Browse files
committed
Db to markdown migration
1 parent adc3723 commit 8a59aa3

6 files changed

Lines changed: 720 additions & 0 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.inspiredandroid.linuxcommandbibliotheca.data
2+
3+
data class BasicCategory(
4+
val id: String, // filename without .md (e.g., "filesfolders")
5+
val title: String // from H1 (e.g., "Files & Folders")
6+
)
7+
8+
data class BasicGroup(
9+
val id: Long, // hash of categoryId+description
10+
val description: String // from H2 (e.g., "Create file")
11+
)
12+
13+
data class BasicCommand(
14+
val id: Long, // hash for stable ID
15+
val command: String, // e.g., "$ touch [fileName]"
16+
val mans: String // e.g., "touch"
17+
)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.inspiredandroid.linuxcommandbibliotheca.data
2+
3+
import android.content.Context
4+
5+
class BasicsRepository(private val context: Context) {
6+
7+
fun getCategories(): List<BasicCategory> {
8+
val assetManager = context.assets
9+
val files = assetManager.list("basics") ?: return emptyList()
10+
11+
return files
12+
.filter { it.endsWith(".md") }
13+
.mapNotNull { filename ->
14+
val id = filename.removeSuffix(".md")
15+
val title = readCategoryTitle(filename)
16+
if (title != null) {
17+
BasicCategory(id = id, title = title)
18+
} else {
19+
null
20+
}
21+
}
22+
.sortedBy { it.title }
23+
}
24+
25+
private fun readCategoryTitle(filename: String): String? {
26+
return try {
27+
context.assets.open("basics/$filename").bufferedReader().useLines { lines ->
28+
lines.firstOrNull { it.startsWith("# ") }?.removePrefix("# ")?.trim()
29+
}
30+
} catch (e: Exception) {
31+
null
32+
}
33+
}
34+
35+
fun getGroupsAndCommands(categoryId: String): Pair<List<BasicGroup>, Map<Long, List<BasicCommand>>> {
36+
val groups = mutableListOf<BasicGroup>()
37+
val commandsByGroupId = mutableMapOf<Long, MutableList<BasicCommand>>()
38+
39+
try {
40+
val content = context.assets.open("basics/$categoryId.md").bufferedReader().readText()
41+
val lines = content.lines()
42+
43+
var currentGroupId: Long? = null
44+
var commandIndex = 0
45+
46+
for (line in lines) {
47+
when {
48+
line.startsWith("## ") -> {
49+
val description = line.removePrefix("## ").trim()
50+
val groupId = (categoryId + description).hashCode().toLong()
51+
groups.add(BasicGroup(id = groupId, description = description))
52+
currentGroupId = groupId
53+
commandsByGroupId[groupId] = mutableListOf()
54+
commandIndex = 0
55+
}
56+
line.contains("<code>") && currentGroupId != null -> {
57+
val (command, mans) = parseCommandLine(line)
58+
val commandId = (categoryId + currentGroupId + commandIndex).hashCode().toLong()
59+
commandsByGroupId[currentGroupId]?.add(
60+
BasicCommand(id = commandId, command = command, mans = mans)
61+
)
62+
commandIndex++
63+
}
64+
}
65+
}
66+
} catch (e: Exception) {
67+
// Return empty on error
68+
}
69+
70+
return Pair(groups, commandsByGroupId)
71+
}
72+
73+
private fun parseCommandLine(line: String): Pair<String, String> {
74+
// Extract command from <code>...</code>
75+
val codeRegex = Regex("<code>(.*?)</code>")
76+
val codeMatch = codeRegex.find(line)
77+
val codeContent = codeMatch?.groupValues?.get(1) ?: ""
78+
79+
// Extract man links from <a href="/man/xxx">
80+
val manRegex = Regex("""<a href="/man/([^"]+)">""")
81+
val mans = manRegex.findAll(codeContent)
82+
.map { it.groupValues[1] }
83+
.distinct()
84+
.joinToString(",")
85+
86+
// Remove HTML tags to get clean command text
87+
val command = codeContent
88+
.replace(Regex("""<a href="[^"]*">"""), "")
89+
.replace("</a>", "")
90+
.replace("&gt;", ">")
91+
.replace("&lt;", "<")
92+
.replace("&amp;", "&")
93+
.trim()
94+
95+
return Pair(command, mans)
96+
}
97+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.inspiredandroid.linuxcommandbibliotheca.data
2+
3+
data class CommandInfo(
4+
val id: Long, // name.hashCode().toLong() for stable ID
5+
val name: String, // filename without .md
6+
)
7+
8+
data class CommandSectionInfo(
9+
val id: Long, // (commandName + title).hashCode().toLong()
10+
val title: String, // "TLDR", "SYNOPSIS", etc.
11+
val content: String,
12+
)
13+
14+
fun CommandSectionInfo.getSortPriority(): Int = when (title) {
15+
"TLDR" -> 0
16+
"SYNOPSIS" -> 10
17+
"SEE ALSO" -> 90
18+
"AUTHOR" -> 100
19+
else -> 50
20+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package com.inspiredandroid.linuxcommandbibliotheca.data
2+
3+
import android.content.Context
4+
5+
class CommandsRepository(private val context: Context) {
6+
7+
private var cachedCommands: List<CommandInfo>? = null
8+
9+
fun getCommands(): List<CommandInfo> {
10+
cachedCommands?.let { return it }
11+
12+
val assetManager = context.assets
13+
val files = assetManager.list("commands") ?: return emptyList()
14+
15+
val commands = files
16+
.filter { it.endsWith(".md") }
17+
.map { filename ->
18+
val name = filename.removeSuffix(".md")
19+
CommandInfo(
20+
id = name.hashCode().toLong(),
21+
name = name,
22+
)
23+
}
24+
.sortedBy { it.name }
25+
26+
cachedCommands = commands
27+
return commands
28+
}
29+
30+
fun getCommandsByQuery(query: String): List<CommandInfo> {
31+
val commands = getCommands()
32+
val lowerQuery = query.lowercase()
33+
34+
return commands
35+
.filter { it.name.lowercase().contains(lowerQuery) }
36+
.sortedWith(compareBy(
37+
// Exact match first
38+
{ it.name.lowercase() != lowerQuery },
39+
// Starts with query second
40+
{ !it.name.lowercase().startsWith(lowerQuery) },
41+
// Then alphabetically
42+
{ it.name }
43+
))
44+
}
45+
46+
fun getCommand(name: String): CommandInfo? {
47+
return getCommands().find { it.name == name }
48+
}
49+
50+
fun hasCommand(name: String): Boolean {
51+
return getCommands().any { it.name == name }
52+
}
53+
54+
fun getSections(commandName: String): List<CommandSectionInfo> {
55+
val sections = mutableListOf<CommandSectionInfo>()
56+
57+
try {
58+
val content = context.assets.open("commands/$commandName.md").bufferedReader().readText()
59+
val lines = content.lines()
60+
61+
var currentTitle: String? = null
62+
val currentContent = StringBuilder()
63+
64+
for (line in lines) {
65+
if (line.startsWith("# ")) {
66+
// Save previous section if exists
67+
if (currentTitle != null) {
68+
sections.add(
69+
CommandSectionInfo(
70+
id = (commandName + currentTitle).hashCode().toLong(),
71+
title = currentTitle,
72+
content = currentContent.toString().trim(),
73+
)
74+
)
75+
}
76+
// Start new section
77+
currentTitle = line.removePrefix("# ").trim()
78+
currentContent.clear()
79+
} else if (currentTitle != null) {
80+
currentContent.appendLine(line)
81+
}
82+
}
83+
84+
// Save the last section
85+
if (currentTitle != null) {
86+
sections.add(
87+
CommandSectionInfo(
88+
id = (commandName + currentTitle).hashCode().toLong(),
89+
title = currentTitle,
90+
content = currentContent.toString().trim(),
91+
)
92+
)
93+
}
94+
} catch (e: Exception) {
95+
// Return empty on error
96+
}
97+
98+
return sections.sortedBy { it.getSortPriority() }
99+
}
100+
}

0 commit comments

Comments
 (0)