@icarusmx/creta 1.5.14 → 1.5.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,197 @@
1
+ import { readFileSync, readdirSync } from 'fs'
2
+ import { join, dirname } from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import chalk from 'chalk'
5
+
6
+ const __filename = fileURLToPath(import.meta.url)
7
+ const __dirname = dirname(__filename)
8
+
9
+ export class ExerciseReader {
10
+ constructor() {
11
+ this.exercisesDir = join(__dirname, '../exercises')
12
+ }
13
+
14
+ /**
15
+ * Get all available exercises
16
+ */
17
+ getAvailableExercises() {
18
+ try {
19
+ const files = readdirSync(this.exercisesDir)
20
+ return files
21
+ .filter(f => f.endsWith('.md') && f !== 'README.md' && f !== 'API_CHANGES.md')
22
+ .map(f => ({
23
+ filename: f,
24
+ id: f.replace('.md', ''),
25
+ number: this.extractNumber(f),
26
+ title: this.extractTitle(f)
27
+ }))
28
+ .sort((a, b) => (a.number || 999) - (b.number || 999))
29
+ } catch (error) {
30
+ return []
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Extract number from filename (e.g., "01-" -> 1)
36
+ */
37
+ extractNumber(filename) {
38
+ const match = filename.match(/^(\d+)-/)
39
+ return match ? parseInt(match[1], 10) : null
40
+ }
41
+
42
+ /**
43
+ * Extract title from filename (e.g., "01-developing-muscle-for-nvim" -> "Developing Muscle for Nvim")
44
+ */
45
+ extractTitle(filename) {
46
+ const withoutExt = filename.replace('.md', '')
47
+ const withoutNumber = withoutExt.replace(/^\d+-/, '')
48
+ return withoutNumber
49
+ .split('-')
50
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
51
+ .join(' ')
52
+ }
53
+
54
+ /**
55
+ * Find exercise by ID (number or slug)
56
+ */
57
+ findExercise(query) {
58
+ const exercises = this.getAvailableExercises()
59
+
60
+ // Try exact match by ID first
61
+ let exercise = exercises.find(e => e.id === query)
62
+ if (exercise) return exercise
63
+
64
+ // Try by number
65
+ const queryNum = parseInt(query, 10)
66
+ if (!isNaN(queryNum)) {
67
+ exercise = exercises.find(e => e.number === queryNum)
68
+ if (exercise) return exercise
69
+ }
70
+
71
+ // Try partial match (fuzzy)
72
+ const queryLower = query.toLowerCase()
73
+ exercise = exercises.find(e =>
74
+ e.id.toLowerCase().includes(queryLower) ||
75
+ e.title.toLowerCase().includes(queryLower)
76
+ )
77
+
78
+ return exercise || null
79
+ }
80
+
81
+ /**
82
+ * Read and display exercise
83
+ */
84
+ read(query) {
85
+ const exercise = this.findExercise(query)
86
+
87
+ if (!exercise) {
88
+ this.showExerciseNotFound(query)
89
+ return false
90
+ }
91
+
92
+ try {
93
+ const filepath = join(this.exercisesDir, exercise.filename)
94
+ const content = readFileSync(filepath, 'utf-8')
95
+
96
+ this.displayExercise(exercise, content)
97
+ return true
98
+ } catch (error) {
99
+ console.error(chalk.red('❌ Error leyendo ejercicio:'), error.message)
100
+ return false
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Display exercise with formatting
106
+ */
107
+ displayExercise(exercise, content) {
108
+ console.clear()
109
+
110
+ // Header
111
+ console.log(chalk.cyan('\n📚 Ejercicio Creta'))
112
+ console.log(chalk.gray('═'.repeat(70)))
113
+ console.log(chalk.bold(`\n${exercise.number ? `${exercise.number}. ` : ''}${exercise.title}`))
114
+ console.log(chalk.gray('─'.repeat(70)))
115
+
116
+ // Content (render markdown as-is, terminal will handle it)
117
+ console.log('\n' + content + '\n')
118
+
119
+ // Footer
120
+ console.log(chalk.gray('─'.repeat(70)))
121
+ console.log(chalk.dim('💡 Tip: Abre este archivo en nvim para usar fold markers: {{{ }}}'))
122
+ console.log(chalk.dim(` Archivo: lib/exercises/${exercise.filename}`))
123
+ console.log(chalk.gray('═'.repeat(70)) + '\n')
124
+ }
125
+
126
+ /**
127
+ * Show error when exercise not found
128
+ */
129
+ showExerciseNotFound(query) {
130
+ console.log(chalk.red(`\n❌ Ejercicio "${query}" no encontrado\n`))
131
+ console.log(chalk.yellow('📚 Ejercicios disponibles:\n'))
132
+
133
+ const exercises = this.getAvailableExercises()
134
+ exercises.forEach(e => {
135
+ const num = e.number ? chalk.cyan(`${e.number}.`.padEnd(4)) : ' '
136
+ const title = chalk.white(e.title)
137
+ const id = chalk.gray(`(${e.id})`)
138
+ console.log(` ${num}${title} ${id}`)
139
+ })
140
+
141
+ console.log(chalk.gray('\n💡 Uso: creta read <número|nombre>'))
142
+ console.log(chalk.gray(' Ejemplos:'))
143
+ console.log(chalk.gray(' creta read 1'))
144
+ console.log(chalk.gray(' creta read gh-fundamentals'))
145
+ console.log(chalk.gray(' creta read nvim\n'))
146
+ }
147
+
148
+ /**
149
+ * List all available exercises
150
+ */
151
+ list() {
152
+ console.log(chalk.cyan('\n📚 Ejercicios Disponibles en Creta'))
153
+ console.log(chalk.gray('═'.repeat(70)))
154
+
155
+ const exercises = this.getAvailableExercises()
156
+
157
+ if (exercises.length === 0) {
158
+ console.log(chalk.yellow('\n⚠️ No hay ejercicios disponibles\n'))
159
+ return
160
+ }
161
+
162
+ console.log()
163
+ exercises.forEach(e => {
164
+ const num = e.number ? chalk.cyan(`${e.number}.`.padEnd(4)) : ' '
165
+ const title = chalk.bold(e.title)
166
+ const id = chalk.gray(`(${e.id})`)
167
+ console.log(` ${num}${title}`)
168
+ console.log(` ${id}`)
169
+ })
170
+
171
+ console.log(chalk.gray('\n💡 Para leer un ejercicio:'))
172
+ console.log(chalk.yellow(' creta read <número|nombre>'))
173
+ console.log(chalk.gray(' Ejemplo: creta read 14\n'))
174
+ }
175
+ }
176
+
177
+ /**
178
+ * CLI entry point
179
+ */
180
+ export async function readExercise(query) {
181
+ const reader = new ExerciseReader()
182
+
183
+ if (!query) {
184
+ reader.list()
185
+ return
186
+ }
187
+
188
+ reader.read(query)
189
+ }
190
+
191
+ /**
192
+ * List exercises CLI entry point
193
+ */
194
+ export async function listExercises() {
195
+ const reader = new ExerciseReader()
196
+ reader.list()
197
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@icarusmx/creta",
3
- "version": "1.5.14",
3
+ "version": "1.5.15",
4
4
  "description": "Salgamos de este laberinto.",
5
5
  "type": "module",
6
6
  "bin": {