OutlineReader.js 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import { promises as fs } from 'node:fs'
  2. import path from 'node:path'
  3. /**
  4. * OutlineReader:读取总纲/卷纲。
  5. */
  6. export class OutlineReader {
  7. constructor(repoPath, cache = null) {
  8. this.repoPath = repoPath
  9. this.cache = cache
  10. }
  11. /**
  12. * 读取总纲指定小节。
  13. * @param {string} sectionTitle - 小节标题(如 "结局")
  14. * @returns {Promise<{ok: boolean, content: string, error: string}>}
  15. */
  16. async readOutlineSection(sectionTitle) {
  17. const outlinePath = path.join(this.repoPath, '大纲', '总纲.md')
  18. try {
  19. const content = await fs.readFile(outlinePath, 'utf8')
  20. const section = this._extractSection(content, sectionTitle)
  21. return { ok: true, content: section, error: '' }
  22. } catch (err) {
  23. return { ok: false, content: '', error: '总纲文件不存在' }
  24. }
  25. }
  26. /**
  27. * 读取卷纲全文。
  28. * @param {number} volumeNum
  29. * @returns {Promise<{ok: boolean, content: string, error: string}>}
  30. */
  31. async readVolumeOutline(volumeNum) {
  32. const volStr = String(volumeNum).padStart(2, '0')
  33. const volumePath = path.join(this.repoPath, '大纲', '卷纲', `第${volStr}卷.md`)
  34. try {
  35. const content = await fs.readFile(volumePath, 'utf8')
  36. return { ok: true, content, error: '' }
  37. } catch (err) {
  38. return { ok: false, content: '', error: `第${volumeNum}卷卷纲不存在` }
  39. }
  40. }
  41. /**
  42. * 列出所有卷纲。
  43. * @returns {Promise<number[]>}
  44. */
  45. async listVolumes() {
  46. const outlineDir = path.join(this.repoPath, '大纲', '卷纲')
  47. try {
  48. const files = await fs.readdir(outlineDir)
  49. const volumes = files
  50. .filter((file) => file.match(/^第\d+卷\.md$/))
  51. .map((file) => {
  52. const match = file.match(/第(\d+)卷/)
  53. return match ? parseInt(match[1], 10) : 0
  54. })
  55. .filter((v) => v > 0)
  56. .sort((a, b) => a - b)
  57. return volumes
  58. } catch (err) {
  59. return []
  60. }
  61. }
  62. _extractSection(content, sectionTitle) {
  63. const lines = content.split('\n')
  64. let inSection = false
  65. const sectionLines = []
  66. for (const line of lines) {
  67. if (line.startsWith('#')) {
  68. if (inSection) break
  69. if (line.includes(sectionTitle)) {
  70. inSection = true
  71. continue
  72. }
  73. }
  74. if (inSection) sectionLines.push(line)
  75. }
  76. return sectionLines.join('\n').trim()
  77. }
  78. }