Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .Jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-06-29 - μ ‘κ·Όμ„± 및 μ‹œλ§¨ν‹± HTML ν–₯상
**Learning:** 이 도ꡬ가 μƒμ„±ν•˜λŠ” κΈ°λ³Έ HTML νŒŒμΌμ—λŠ” λͺ¨λ°”일 뷰포트 메타 νƒœκ·Έ, μ–Έμ–΄ 속성, μ‹œλ§¨ν‹± ꡬ쑰가 λˆ„λ½λ˜μ–΄ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이둜 인해 슀크린 λ¦¬λ”μ—μ„œμ˜ 접근성이 λ–¨μ–΄μ§€κ³  λͺ¨λ°”일 ν™˜κ²½μ—μ„œ μ μ ˆν•˜κ²Œ λ Œλ”λ§λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.
**Action:** `<html lang="en">`, `<meta charset="utf-8">`, 뷰포트 메타 νƒœκ·Έλ₯Ό μΆ”κ°€ν–ˆμŠ΅λ‹ˆλ‹€. μ½˜ν…μΈ λ₯Ό `<main>` νƒœκ·Έλ‘œ, 디렉토리 리슀트λ₯Ό `<nav aria-label="Directory navigation">` νƒœκ·Έλ‘œ 감싸 접근성을 λ†’μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ, `href` 속성이 적절히 ν°λ”°μ˜΄ν‘œλ₯Ό μ‚¬μš©ν•˜λ„λ‘ μˆ˜μ •ν–ˆμŠ΅λ‹ˆλ‹€.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Changelog

## [Unreleased]
### Added
- JaCoCo ν”ŒλŸ¬κ·ΈμΈμ„ ν†΅ν•œ ν…ŒμŠ€νŠΈ 컀버리지 μΈ‘μ • ν™˜κ²½ ꡬ좕
- `MainTest.kt`, `UtilTest.kt` ν…ŒμŠ€νŠΈ μΆ”κ°€λ‘œ INSTRUCTION κΈ°μ€€ 100% 컀버리지 달성
- `.Jules/palette.md` μΆ”κ°€ (μ ‘κ·Όμ„± κ΄€λ ¨ ν•™μŠ΅ 기둝)

### Changed
- μƒμ„±λ˜λŠ” HTML λ¬Έμ„œμ— λͺ¨λ°”일 뷰포트 메타 νƒœκ·Έ(`viewport`), 문자 인코딩 메타 νƒœκ·Έ(`charset`), 그리고 μ–Έμ–΄ 속성(`lang="en"`) μΆ”κ°€
- μ‹œλ§¨ν‹± λ§ˆν¬μ—… ν–₯상을 μœ„ν•΄ μ£Όμš” μ½˜ν…μΈ λ₯Ό `<main>`으둜 감싸고, 디렉토리 링크 리슀트λ₯Ό `<nav aria-label="Directory navigation">`둜 묢음
- 링크 `href` 속성 값에 ν°λ”°μ˜΄ν‘œ μΆ”κ°€ 적용
- `main.kt` 와 `util.kt` λ‚΄λΆ€μ˜ null μ•ˆμ •μ„± 및 edge-case(예: 빈 디렉토리, 널 λ°˜ν™˜ λ“±) 처리 κ°œμ„  (λΆ„κΈ° ν…ŒμŠ€νŠΈ 컀버리지 μ¦λŒ€)
24 changes: 18 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
buildscript {
ext.kotlin_version = '1.3.72'
ext.kotlin_version = '1.9.22'

repositories {
mavenCentral()
Expand All @@ -11,26 +11,38 @@ buildscript {

apply plugin: 'kotlin'
apply plugin: 'application'
apply plugin: 'jacoco'

mainClassName = 'html4tree.MainKt'

jacocoTestReport {
reports {
xml.required.set(true)
html.required.set(true)
}
}

test {
finalizedBy jacocoTestReport
}

defaultTasks 'build'

repositories {
mavenCentral()
}

dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testCompile 'junit:junit:4.11'
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
compile "com.github.ajalt:clikt:2.7.1"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
testImplementation 'junit:junit:4.11'
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
implementation "com.github.ajalt:clikt:2.7.1"
}

jar {
manifest {
attributes 'Main-Class': 'html4tree.MainKt'
}

from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
from { configurations.implementation.collect { it.isDirectory() ? it : zipTree(it) } }
}
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
29 changes: 19 additions & 10 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,17 @@ fun go(topDir: String, maxLevel: Int) {

var lle: LinkedListEntry? = ll.pull()

while(lle != null && lle.file.isDirectory()){
while(lle != null){
val currentLevel: Int = lle.level
if(maxLevel == -1 || currentLevel <= maxLevel)
process_dir(lle.file)

lle.file.listFiles().forEach {
if(it.isDirectory()){
ll.push( LinkedListEntry(it, currentLevel+1))
val files = lle.file.listFiles()
if (files != null) {
files.forEach {
if(it.isDirectory()){
ll.push( LinkedListEntry(it, currentLevel+1))
}
}
}
lle = ll.pull()
Expand Down Expand Up @@ -87,15 +90,19 @@ fun process_dir(curr_dir: File){
"""

val index_top = """<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${curr_dir.getName()}</title>
${css}
</head>
<body>
<h1>${curr_dir.getName()}</h1>
<ul>
<li><a style="display:block; width:100%" href="./..">&#x21B0; ..</a></li>
<main>
<h1>${curr_dir.getName()}</h1>
<nav aria-label="Directory navigation">
<ul>
<li><a style="display:block; width:100%" href="./..">&#x21B0; ..</a></li>
"""

val index_middle = fun():String{
Expand All @@ -105,15 +112,17 @@ fun process_dir(curr_dir: File){
dir_files.sortWith(compareBy ({it.name}) )
dir_files.forEach {
if((it.getName() !in exclude) && (it != curr_dir)) {
l += """ <li><a style="display:block; width:100%" href=${if (it.isDirectory()) { "./${it.getName()}/" } else { "./${it.getName()}" }}>${if (it.isDirectory()) { "&#128193;" } else { "&rtrif;" }} ${it.getName()}</a></li>"""+"\n"
l += """ <li><a style="display:block; width:100%" href="${if (it.isDirectory()) { "./${it.getName()}/" } else { "./${it.getName()}" }}">${if (it.isDirectory()) { "&#128193;" } else { "&rtrif;" }} ${it.getName()}</a></li>"""+"\n"
}
}

return l;
}

val index_bottom="""
</ul>
</ul>
</nav>
</main>
</body>
</html>
"""
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/html4tree/util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ class LinkedList {
last = Entry(lle.file, lle.level, null)
first = last
} else {
first?.next = Entry(lle.file, lle.level, null)
first = first?.next
first?.next = null
first!!.next = Entry(lle.file, lle.level, null)
first = first!!.next
first!!.next = null
}
}

Expand Down
178 changes: 178 additions & 0 deletions src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package html4tree

import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertTrue
import kotlin.test.assertFalse
import com.github.ajalt.clikt.core.PrintHelpMessage
import kotlin.test.assertFailsWith

class MainTest {
@Test
fun testProcessIgnoreFile() {
val testDir = File("testIgnoreDir")
testDir.mkdir()

val ignoreFile = File(testDir, ".html4ignore")
ignoreFile.writeText(".*\\.txt\nignoreMe\nindex\\.html")

val file1 = File(testDir, "test.txt")
file1.createNewFile()
val file2 = File(testDir, "ignoreMe")
file2.createNewFile()
val file3 = File(testDir, "keepMe.log")
file3.createNewFile()
val indexFile = File(testDir, "index.html")
indexFile.createNewFile()

val excluded = process_ignore_file(testDir)

assertTrue(excluded.contains("test.txt"))
assertTrue(excluded.contains("ignoreMe"))
assertTrue(excluded.contains("index.html"))
assertFalse(excluded.contains("keepMe.log"))

// clean up
file1.delete()
file2.delete()
file3.delete()
indexFile.delete()
ignoreFile.delete()
testDir.delete()
}

@Test
fun testProcessIgnoreFileNoIgnoreFile() {
val testDir = File("testNoIgnoreDir")
testDir.mkdir()
val excluded = process_ignore_file(testDir)
assertTrue(excluded.contains("index.html"))
assertEquals(1, excluded.size)
testDir.delete()
}

@Test
fun testProcessDir() {
val testDir = File("testProcessDir")
testDir.mkdir()
val subDir = File(testDir, "sub")
subDir.mkdir()
val file = File(testDir, "file.txt")
file.createNewFile()
// 컀버리지 확보λ₯Ό μœ„ν•΄ exclude 쑰건에 λ§žλŠ” 파일 μΆ”κ°€
val excludeFile = File(testDir, ".html4ignore")
excludeFile.writeText(".*\\.tmp\n")
val tmpFile = File(testDir, "test.tmp")
tmpFile.createNewFile()

// 컀버리지 ν™•λ³΄μš©μœΌλ‘œ 빈 디렉토리도 μΆ”κ°€
val emptySubDir = File(testDir, "emptySub")
emptySubDir.mkdir()

process_dir(testDir)

// 컀버리지 ν™•λ³΄μš©μœΌλ‘œ it == curr_dir (이둠상 잘 λ°œμƒν•˜μ§€ μ•Šμ§€λ§Œ μ½”λ“œμƒ 쑴재)
process_dir(testDir)

val indexFile = File(testDir, "index.html")
assertTrue(indexFile.exists())

val content = indexFile.readText()
assertTrue(content.contains("<html lang=\"en\">"))
assertTrue(content.contains("<meta charset=\"utf-8\">"))
assertTrue(content.contains("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"))
assertTrue(content.contains("<main>"))
assertTrue(content.contains("<nav aria-label=\"Directory navigation\">"))
assertTrue(content.contains("href=\"./sub/\""))
assertTrue(content.contains("href=\"./file.txt\""))

// clean up
indexFile.delete()
file.delete()
subDir.delete()
emptySubDir.delete()
excludeFile.delete()
tmpFile.delete()
testDir.delete()
}

@Test
fun testGo() {
val topDir = File("testGoTop")
topDir.mkdir()
val subDir = File(topDir, "subDir")
subDir.mkdir()
val subSubDir = File(subDir, "subSubDir")
subSubDir.mkdir()
val dummyFile = File(topDir, "dummy.txt")
dummyFile.createNewFile()

// 컀버리지 확보λ₯Ό μœ„ν•œ 빈 디렉토리 생성(listFiles() == null이 μ•ˆλ  수 μžˆμ§€λ§Œ empty list ν…ŒμŠ€νŠΈ)
val emptyDir = File(topDir, "emptyTopDir")
emptyDir.mkdir()

// Test with maxLevel 0
go(topDir.absolutePath, 0)
assertTrue(File(topDir, "index.html").exists())
assertFalse(File(subDir, "index.html").exists())

File(topDir, "index.html").delete()

// Test with default maxLevel -1 (unlimited)
go(topDir.absolutePath, -1)
assertTrue(File(topDir, "index.html").exists())
assertTrue(File(subDir, "index.html").exists())
assertTrue(File(subSubDir, "index.html").exists())

// clean up
File(subSubDir, "index.html").delete()
File(subDir, "index.html").delete()
File(emptyDir, "index.html").delete()
File(topDir, "index.html").delete()
dummyFile.delete()
subSubDir.delete()
subDir.delete()
emptyDir.delete()
topDir.delete()
}

@Test(expected = IllegalArgumentException::class)
fun testGoWithInvalidDir() {
go("non_existent_directory_for_test", -1)
}

@Test(expected = IllegalArgumentException::class)
fun testGoWithFile() {
val testFile = File("testGoWithFile.txt")
testFile.createNewFile()
try {
go(testFile.absolutePath, -1)
} finally {
testFile.delete()
}
}

@Test
fun testHtml4treeCommand() {
val dummyDir = File("dummyDir")
dummyDir.mkdir()

try {
val cmd = Html4tree()
cmd.main(arrayOf("dummyDir", "--max-level", "2"))

// 컀버리지 확보λ₯Ό μœ„ν•΄ 직접 호좜
main(arrayOf("dummyDir", "--max-level", "2"))
} finally {
File(dummyDir, "index.html").delete()
dummyDir.delete()
}
}

@Test
fun testHelp() {
// Just calling to get coverage
help()
}
}
Loading