Skip to content

Commit 77f208e

Browse files
committed
Fix # inside code parsing
1 parent b33c05d commit 77f208e

File tree

7 files changed

+265
-331
lines changed

7 files changed

+265
-331
lines changed

android/src/main/java/com/inspiredandroid/linuxcommandbibliotheca/data/BasicsRepository.kt

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.inspiredandroid.linuxcommandbibliotheca.data
22

33
import android.content.Context
4+
import com.linuxcommandlibrary.shared.MarkdownParser
45
import com.linuxcommandlibrary.shared.basicsSortOrder
56

67
class BasicsRepository(private val context: Context) {
@@ -37,25 +38,25 @@ class BasicsRepository(private val context: Context) {
3738

3839
try {
3940
val content = context.assets.open("basics/$categoryId.md").bufferedReader().readText()
40-
val lines = content.lines()
41-
42-
var currentGroupId: Long? = null
43-
var commandIndex = 0
44-
45-
for (line in lines) {
46-
when {
47-
line.startsWith("## ") -> {
48-
val description = line.removePrefix("## ").trim()
49-
val groupId = (categoryId + description).hashCode().toLong()
50-
groups.add(BasicGroup(id = groupId, description = description))
51-
currentGroupId = groupId
52-
commandsByGroupId[groupId] = mutableListOf()
53-
commandIndex = 0
54-
}
55-
line.trim().startsWith("```") && line.trim().endsWith("```") && currentGroupId != null -> {
41+
42+
// Use shared parser which properly handles code blocks when splitting by headers
43+
val groupSections = MarkdownParser.splitByHeaders(content, "## ")
44+
45+
for ((description, groupContent) in groupSections) {
46+
val groupId = (categoryId + description).hashCode().toLong()
47+
groups.add(BasicGroup(id = groupId, description = description))
48+
commandsByGroupId[groupId] = mutableListOf()
49+
50+
// Extract commands from the group content
51+
var commandIndex = 0
52+
val lines = groupContent.lines()
53+
54+
for (line in lines) {
55+
// Handle inline code blocks (```command```)
56+
if (line.trim().startsWith("```") && line.trim().endsWith("```") && line.trim().length > 6) {
5657
val (command, mans) = parseCommandLine(line)
57-
val commandId = (categoryId + currentGroupId + commandIndex).hashCode().toLong()
58-
commandsByGroupId[currentGroupId]?.add(
58+
val commandId = (categoryId + groupId + commandIndex).hashCode().toLong()
59+
commandsByGroupId[groupId]?.add(
5960
BasicCommand(id = commandId, command = command, mans = mans),
6061
)
6162
commandIndex++
Lines changed: 13 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.inspiredandroid.linuxcommandbibliotheca.data
22

33
import android.content.Context
4+
import com.linuxcommandlibrary.shared.MarkdownParser
45

56
class CommandsRepository(private val context: Context) {
67

@@ -47,50 +48,18 @@ class CommandsRepository(private val context: Context) {
4748

4849
fun hasCommand(name: String): Boolean = getCommands().any { it.name == name }
4950

50-
fun getSections(commandName: String): List<CommandSectionInfo> {
51-
val sections = mutableListOf<CommandSectionInfo>()
51+
fun getSections(commandName: String): List<CommandSectionInfo> = try {
52+
val content = context.assets.open("commands/$commandName.md").bufferedReader().readText()
5253

53-
try {
54-
val content = context.assets.open("commands/$commandName.md").bufferedReader().readText()
55-
val lines = content.lines()
56-
57-
var currentTitle: String? = null
58-
val currentContent = StringBuilder()
59-
60-
for (line in lines) {
61-
if (line.startsWith("# ")) {
62-
// Save previous section if exists
63-
if (currentTitle != null) {
64-
sections.add(
65-
CommandSectionInfo(
66-
id = (commandName + currentTitle).hashCode().toLong(),
67-
title = currentTitle,
68-
content = currentContent.toString().trim(),
69-
),
70-
)
71-
}
72-
// Start new section
73-
currentTitle = line.removePrefix("# ").trim()
74-
currentContent.clear()
75-
} else if (currentTitle != null) {
76-
currentContent.appendLine(line)
77-
}
78-
}
79-
80-
// Save the last section
81-
if (currentTitle != null) {
82-
sections.add(
83-
CommandSectionInfo(
84-
id = (commandName + currentTitle).hashCode().toLong(),
85-
title = currentTitle,
86-
content = currentContent.toString().trim(),
87-
),
88-
)
89-
}
90-
} catch (e: Exception) {
91-
// Return empty on error
92-
}
93-
94-
return sections.sortedBy { it.getSortPriority() }
54+
// Use shared parser which properly handles code blocks
55+
MarkdownParser.splitByHeaders(content, "# ").map { (title, sectionContent) ->
56+
CommandSectionInfo(
57+
id = (commandName + title).hashCode().toLong(),
58+
title = title,
59+
content = sectionContent,
60+
)
61+
}.sortedBy { it.getSortPriority() }
62+
} catch (e: Exception) {
63+
emptyList()
9564
}
9665
}

assets/basics/systeminformation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# System information
22

33
## CPU usage in percentage
4-
```HZ=`[awk](/man/awk) '/\#define HZ/ { print $3}' /usr/include/asm-generic/param.h`; NUMCPUS=`[grep](/man/grep) ^proc /proc/cpuinfo | [wc](/man/wc) -l`; FIRST=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; [sleep](/man/sleep) 1; SECOND=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; USED=`[echo](/man/echo) 4 k 100 $SECOND $FIRST - $NUMCPUS $HZ \* / 100 \* - p | [dc](/man/dc)`; [echo](/man/echo) ${USED}`
4+
```HZ=`[awk](/man/awk) '/\#define HZ/ { print $3}' /usr/include/asm-generic/param.h`; NUMCPUS=`[grep](/man/grep) ^proc /proc/cpuinfo | [wc](/man/wc) -l`; FIRST=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; [sleep](/man/sleep) 1; SECOND=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; USED=`[echo](/man/echo) 4 k 100 $SECOND $FIRST - $NUMCPUS $HZ \* / 100 \* - p | [dc](/man/dc)`; [echo](/man/echo) ${USED}` ```
55
```NUMCPUS=`[grep](/man/grep) ^proc /proc/cpuinfo | [wc](/man/wc) -l`; FIRST=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; [sleep](/man/sleep) 1; SECOND=`[cat](/man/cat) /proc/stat | [awk](/man/awk) '/^cpu / { print $5}'`; USED=`[echo](/man/echo) 2 k 100 $SECOND $FIRST - $NUMCPUS / - p | [dc](/man/dc)`; [echo](/man/echo) ${USED}%```
66

77
## Laptop battery in percentage

cli/src/main/kotlin/com/linuxcommandlibrary/cli/ConsoleApplication.kt

Lines changed: 26 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.linuxcommandlibrary.cli
22

3+
import com.linuxcommandlibrary.shared.BasicInfo
34
import com.linuxcommandlibrary.shared.MarkdownParser
5+
import com.linuxcommandlibrary.shared.TipInfo
46
import com.linuxcommandlibrary.shared.TipSectionElement
57
import com.linuxcommandlibrary.shared.Version
68
import kotlin.system.exitProcess
@@ -10,10 +12,9 @@ const val ITALIC = "\u001b[3m"
1012
const val RESET = "\u001b[0m"
1113

1214
// Data classes for CLI
13-
data class CommandInfo(val name: String)
15+
data class CommandInfoSimple(val name: String)
1416
data class CommandSection(val title: String, val content: String)
1517
data class BasicCategory(val id: String, val title: String)
16-
data class TipInfo(val id: Long, val title: String, val sections: List<TipSectionElement>)
1718

1819
fun main() {
1920
showIntro()
@@ -57,15 +58,15 @@ private fun showStartMenu() {
5758
private fun readResource(path: String): String? = object {}.javaClass.getResourceAsStream("/$path")?.bufferedReader()?.readText()
5859

5960
// Command functions
60-
private fun getCommands(): List<CommandInfo> {
61+
private fun getCommands(): List<CommandInfoSimple> {
6162
val index = readResource("commands.index") ?: return emptyList()
6263
return index.lines()
6364
.filter { it.isNotBlank() }
64-
.map { CommandInfo(it) }
65+
.map { CommandInfoSimple(it) }
6566
.sortedBy { it.name }
6667
}
6768

68-
private fun getCommandsByQuery(query: String): List<CommandInfo> {
69+
private fun getCommandsByQuery(query: String): List<CommandInfoSimple> {
6970
val lowerQuery = query.lowercase()
7071
return getCommands()
7172
.filter { it.name.lowercase().contains(lowerQuery) }
@@ -79,30 +80,12 @@ private fun getCommandsByQuery(query: String): List<CommandInfo> {
7980
}
8081

8182
private fun getSections(commandName: String): List<CommandSection> {
82-
val sections = mutableListOf<CommandSection>()
83-
val content = readResource("commands/$commandName.md") ?: return sections
83+
val content = readResource("commands/$commandName.md") ?: return emptyList()
8484

85-
val lines = content.lines()
86-
var currentTitle: String? = null
87-
val currentContent = StringBuilder()
88-
89-
for (line in lines) {
90-
if (line.startsWith("# ")) {
91-
if (currentTitle != null) {
92-
sections.add(CommandSection(currentTitle, currentContent.toString().trim()))
93-
}
94-
currentTitle = line.removePrefix("# ").trim()
95-
currentContent.clear()
96-
} else if (currentTitle != null) {
97-
currentContent.appendLine(line)
98-
}
99-
}
100-
101-
if (currentTitle != null) {
102-
sections.add(CommandSection(currentTitle, currentContent.toString().trim()))
103-
}
104-
105-
return sections.sortedBy { getSortPriority(it.title) }
85+
// Use the shared parser which properly handles code blocks
86+
return MarkdownParser.splitByHeaders(content, "# ").map { (title, sectionContent) ->
87+
CommandSection(title, sectionContent)
88+
}.sortedBy { getSortPriority(it.title) }
10689
}
10790

10891
private fun getSortPriority(title: String): Int = when (title.uppercase()) {
@@ -215,25 +198,24 @@ private fun showBasicGroups(categoryId: String) {
215198
return
216199
}
217200

218-
val lines = content.lines()
219-
var currentGroup: String? = null
220-
221-
for (line in lines) {
222-
when {
223-
line.startsWith("## ") -> {
224-
if (currentGroup != null) println()
225-
currentGroup = line.removePrefix("## ").trim()
226-
println("$BOLD$currentGroup$RESET")
227-
}
228-
line.trim().startsWith("```") && currentGroup != null -> {
229-
val code = line.trim().removeSurrounding("```")
230-
.replace(Regex("\\[([^\\]]+)]\\(/man/[^)]+\\)")) { it.groupValues[1] } // Remove man links
231-
println("- $ $code")
201+
// Use the shared parser which properly handles code blocks
202+
val basicInfo: BasicInfo = MarkdownParser.parseBasic(content)
203+
204+
basicInfo.groups.forEach { group ->
205+
println("$BOLD${group.description}$RESET")
206+
group.sections.forEach { section ->
207+
when (section) {
208+
is TipSectionElement.Code -> {
209+
val code = section.command
210+
.replace(Regex("\\[([^\\]]+)]\\(/man/[^)]+\\)")) { it.groupValues[1] } // Remove man links
211+
println("- $ $code")
212+
}
213+
else -> {} // Skip non-code sections in basic groups display
232214
}
233215
}
216+
println()
234217
}
235218

236-
println()
237219
println("Press enter")
238220
readlnOrNull()
239221
showBasicCategories()
@@ -242,30 +224,7 @@ private fun showBasicGroups(categoryId: String) {
242224
// Tips functions
243225
private fun getTips(): List<TipInfo> {
244226
val content = readResource("tips.md") ?: return emptyList()
245-
val tips = mutableListOf<TipInfo>()
246-
247-
val tipBlocks = content.split(Regex("(?=^## )", RegexOption.MULTILINE))
248-
.filter { it.trim().startsWith("## ") }
249-
250-
for (block in tipBlocks) {
251-
val lines = block.lines()
252-
val titleLine = lines.firstOrNull() ?: continue
253-
val title = titleLine.removePrefix("## ").trim()
254-
if (title.isEmpty()) continue
255-
256-
val contentLines = lines.drop(1).joinToString("\n")
257-
val sections = MarkdownParser.parseMarkdownContent(contentLines)
258-
259-
tips.add(
260-
TipInfo(
261-
id = title.hashCode().toLong(),
262-
title = title,
263-
sections = sections,
264-
),
265-
)
266-
}
267-
268-
return tips
227+
return MarkdownParser.parseTips(content)
269228
}
270229

271230
private fun showTips() {
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.linuxcommandlibrary.shared
2+
3+
/**
4+
* Data class for parsed tip from markdown.
5+
*/
6+
data class TipInfo(
7+
val id: Long,
8+
val title: String,
9+
val sections: List<TipSectionElement>,
10+
)
11+
12+
/**
13+
* Data class for parsed basic category from markdown.
14+
*/
15+
data class BasicInfo(
16+
val title: String,
17+
val groups: List<BasicGroup>,
18+
)
19+
20+
/**
21+
* Data class for parsed basic category group from markdown.
22+
*/
23+
data class BasicGroup(
24+
val id: Long,
25+
val description: String,
26+
val sections: List<TipSectionElement>,
27+
)
28+
29+
/**
30+
* Data class for command parsed from markdown.
31+
*/
32+
data class CommandInfo(
33+
val name: String,
34+
val description: String,
35+
val sections: List<CommandSectionInfo>,
36+
)
37+
38+
/**
39+
* Data class for a section of a command.
40+
*/
41+
data class CommandSectionInfo(
42+
val title: String,
43+
val content: String,
44+
val elements: List<TipSectionElement>,
45+
)
46+
47+
/**
48+
* Get sort priority for command sections.
49+
* TLDR first, SEE ALSO and AUTHOR/HISTORY last.
50+
*/
51+
fun CommandSectionInfo.getSortPriority(): Int = when (title.uppercase()) {
52+
"TLDR" -> 0
53+
"NAME" -> 1
54+
"SYNOPSIS" -> 2
55+
"DESCRIPTION" -> 3
56+
"PARAMETERS" -> 4
57+
"OPTIONS" -> 5
58+
"EXAMPLES" -> 6
59+
"CAVEATS" -> 90
60+
"HISTORY" -> 91
61+
"AUTHOR" -> 92
62+
"SEE ALSO" -> 93
63+
else -> 50
64+
}

0 commit comments

Comments
 (0)