expxdev 0.4.0 → 0.5.0
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.
- package/README.md +27 -9
- package/dist/cli/expx.js +2 -0
- package/dist/cli/expx.js.map +1 -1
- package/dist/cli/init.js +9 -3
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/principal.js +30 -6
- package/dist/cli/principal.js.map +1 -1
- package/dist/cli/selecao.js +3 -0
- package/dist/cli/selecao.js.map +1 -1
- package/dist/cli/subcomandos.d.ts +1 -1
- package/dist/cli/subcomandos.js +3 -2
- package/dist/cli/subcomandos.js.map +1 -1
- package/dist/nucleo/caminhos.d.ts +1 -1
- package/dist/nucleo/catalogo.d.ts +1 -1
- package/dist/nucleo/catalogo.js +7 -1
- package/dist/nucleo/catalogo.js.map +1 -1
- package/dist/nucleo/layout.d.ts +2 -2
- package/dist/nucleo/versao.d.ts +1 -1
- package/dist/parser/esquema/enums.d.ts +3 -2
- package/dist/parser/esquema/enums.js +3 -2
- package/dist/parser/esquema/enums.js.map +1 -1
- package/dist/parser/esquema/evento.d.ts +1 -0
- package/dist/servidor/http.d.ts +11 -0
- package/dist/servidor/http.js +31 -1
- package/dist/servidor/http.js.map +1 -1
- package/dist/watch/desenho/arvore.d.ts +3 -0
- package/dist/watch/desenho/arvore.js +62 -0
- package/dist/watch/desenho/arvore.js.map +1 -0
- package/dist/watch/desenho/bloqueios.d.ts +5 -0
- package/dist/watch/desenho/bloqueios.js +41 -0
- package/dist/watch/desenho/bloqueios.js.map +1 -0
- package/dist/watch/desenho/cabecalho.d.ts +11 -0
- package/dist/watch/desenho/cabecalho.js +49 -0
- package/dist/watch/desenho/cabecalho.js.map +1 -0
- package/dist/watch/desenho/cor.d.ts +44 -0
- package/dist/watch/desenho/cor.js +66 -0
- package/dist/watch/desenho/cor.js.map +1 -0
- package/dist/watch/desenho/desenhar.d.ts +16 -0
- package/dist/watch/desenho/desenhar.js +54 -0
- package/dist/watch/desenho/desenhar.js.map +1 -0
- package/dist/watch/desenho/eventos.d.ts +3 -0
- package/dist/watch/desenho/eventos.js +37 -0
- package/dist/watch/desenho/eventos.js.map +1 -0
- package/dist/watch/desenho/largura.d.ts +23 -0
- package/dist/watch/desenho/largura.js +42 -0
- package/dist/watch/desenho/largura.js.map +1 -0
- package/dist/watch/desenho/lista.d.ts +9 -0
- package/dist/watch/desenho/lista.js +28 -0
- package/dist/watch/desenho/lista.js.map +1 -0
- package/dist/watch/fontes/cauda.d.ts +14 -0
- package/dist/watch/fontes/cauda.js +49 -0
- package/dist/watch/fontes/cauda.js.map +1 -0
- package/dist/watch/fontes/estado-schema.d.ts +79 -0
- package/dist/watch/fontes/estado-schema.js +72 -0
- package/dist/watch/fontes/estado-schema.js.map +1 -0
- package/dist/watch/fontes/estado.d.ts +18 -0
- package/dist/watch/fontes/estado.js +38 -0
- package/dist/watch/fontes/estado.js.map +1 -0
- package/dist/watch/fontes/observar.d.ts +32 -0
- package/dist/watch/fontes/observar.js +101 -0
- package/dist/watch/fontes/observar.js.map +1 -0
- package/dist/watch/fontes/raiz.d.ts +12 -0
- package/dist/watch/fontes/raiz.js +25 -0
- package/dist/watch/fontes/raiz.js.map +1 -0
- package/dist/watch/fontes/rastro.d.ts +17 -0
- package/dist/watch/fontes/rastro.js +43 -0
- package/dist/watch/fontes/rastro.js.map +1 -0
- package/dist/watch/opcoes.d.ts +25 -0
- package/dist/watch/opcoes.js +73 -0
- package/dist/watch/opcoes.js.map +1 -0
- package/dist/watch/principal.d.ts +12 -0
- package/dist/watch/principal.js +49 -0
- package/dist/watch/principal.js.map +1 -0
- package/dist/watch/terminal/restaurar.d.ts +24 -0
- package/dist/watch/terminal/restaurar.js +58 -0
- package/dist/watch/terminal/restaurar.js.map +1 -0
- package/dist/watch/terminal/tela.d.ts +17 -0
- package/dist/watch/terminal/tela.js +64 -0
- package/dist/watch/terminal/tela.js.map +1 -0
- package/dist/watch/visao/escolher.d.ts +18 -0
- package/dist/watch/visao/escolher.js +57 -0
- package/dist/watch/visao/escolher.js.map +1 -0
- package/dist/watch/visao/projetar.d.ts +51 -0
- package/dist/watch/visao/projetar.js +38 -0
- package/dist/watch/visao/projetar.js.map +1 -0
- package/dist/watch/watch.d.ts +26 -0
- package/dist/watch/watch.js +89 -0
- package/dist/watch/watch.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { cortar } from "./largura.js";
|
|
2
|
+
/**
|
|
3
|
+
* A árvore: sprints, fases e tasks, indentadas.
|
|
4
|
+
*
|
|
5
|
+
* Os dois vocabulários de status NÃO são intercambiáveis: task usa o feminino
|
|
6
|
+
* (`concluida`, `bloqueada`) e trabalho/sprint/fase usam o masculino
|
|
7
|
+
* (`concluido`, `bloqueado`). Uma função de marcador que aceitasse `string`
|
|
8
|
+
* casaria os dois por engano ou deixaria um sem cor — por isso são duas
|
|
9
|
+
* (base/schema-v1-e-kinds.md, risco 4).
|
|
10
|
+
*
|
|
11
|
+
* Marcadores são caractere simples, sem emoji, como manda a especificação.
|
|
12
|
+
*/
|
|
13
|
+
const MARCA_TASK = {
|
|
14
|
+
concluida: { marca: "[x]", papel: "sucesso" },
|
|
15
|
+
em_andamento: { marca: "[>]", papel: "atencao" },
|
|
16
|
+
bloqueada: { marca: "[!]", papel: "erro" },
|
|
17
|
+
pendente: { marca: "[ ]", papel: "apagado" },
|
|
18
|
+
};
|
|
19
|
+
const MARCA_GRUPO = {
|
|
20
|
+
concluido: { marca: "[x]", papel: "sucesso" },
|
|
21
|
+
em_andamento: { marca: "[>]", papel: "atencao" },
|
|
22
|
+
bloqueado: { marca: "[!]", papel: "erro" },
|
|
23
|
+
nao_iniciado: { marca: "[ ]", papel: "apagado" },
|
|
24
|
+
};
|
|
25
|
+
export function desenharArvore(v, colunas, pintar) {
|
|
26
|
+
if (v.trabalho === null)
|
|
27
|
+
return [];
|
|
28
|
+
const linhas = [];
|
|
29
|
+
// A task em andamento é o que a pessoa quer achar de relance na tela.
|
|
30
|
+
const emFoco = v.estado?.task ?? null;
|
|
31
|
+
for (const sprint of v.trabalho.sprints) {
|
|
32
|
+
const s = MARCA_GRUPO[sprint.status];
|
|
33
|
+
linhas.push(pintar(cortar(`${s.marca} ${sprint.sprint_id} · ${sprint.titulo}`, colunas), s.papel));
|
|
34
|
+
for (const fase of sprint.fases) {
|
|
35
|
+
const f = MARCA_GRUPO[fase.status];
|
|
36
|
+
// fase paralela mostra com quem roda junto — o plano declarou, a
|
|
37
|
+
// execução nunca decide isso sozinha
|
|
38
|
+
const par = fase.paralela_com.length > 0 ? ` || ${fase.paralela_com.join(",")}` : "";
|
|
39
|
+
linhas.push(pintar(cortar(` ${f.marca} ${fase.id} · ${fase.titulo}${par}`, colunas), f.papel));
|
|
40
|
+
for (const task of fase.tasks) {
|
|
41
|
+
const t = MARCA_TASK[task.status];
|
|
42
|
+
const dep = task.depende_de.length > 0 ? ` ← ${task.depende_de.join(",")}` : "";
|
|
43
|
+
const paralela = task.paralelizavel ? " ||" : "";
|
|
44
|
+
const foco = task.id === emFoco ? " <" : "";
|
|
45
|
+
const texto = ` ${t.marca} ${task.id} ${task.titulo}${paralela}${dep}${foco}`;
|
|
46
|
+
linhas.push(pintar(cortar(texto, colunas), task.id === emFoco ? "destaque" : t.papel));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// Tasks que o arquivo de fases não referencia continuam existindo: o
|
|
50
|
+
// vínculo autoritativo é o campo `fase` da task, e uma task apontando
|
|
51
|
+
// para fase inexistente sumiria da tela sem isto.
|
|
52
|
+
const nasFases = new Set(sprint.fases.flatMap((f) => f.tasks.map((t) => t.id)));
|
|
53
|
+
for (const task of sprint.tasks) {
|
|
54
|
+
if (nasFases.has(task.id))
|
|
55
|
+
continue;
|
|
56
|
+
const t = MARCA_TASK[task.status];
|
|
57
|
+
linhas.push(pintar(cortar(` ${t.marca} ${task.id} ${task.titulo} (fora de fase)`, colunas), t.papel));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return linhas;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=arvore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arvore.js","sourceRoot":"","sources":["../../../src/watch/desenho/arvore.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;;;;;GAUG;AAEH,MAAM,UAAU,GAAwD;IACtE,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;IAC7C,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;IAChD,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE;IAC1C,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;CAC7C,CAAC;AAEF,MAAM,WAAW,GAA4D;IAC3E,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;IAC7C,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;IAChD,SAAS,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE;IAC1C,YAAY,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE;CACjD,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,CAAQ,EAAE,OAAe,EAAE,MAAc;IACtE,IAAI,CAAC,CAAC,QAAQ,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,sEAAsE;IACtE,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,IAAI,IAAI,CAAC;IAEtC,KAAK,MAAM,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,MAAM,MAAM,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CACtF,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACnC,iEAAiE;YACjE,qCAAqC;YACrC,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrF,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,MAAM,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CACnF,CAAC;YAEF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;gBAC9B,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAClC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChF,MAAM,QAAQ,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACjD,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,GAAG,QAAQ,GAAG,GAAG,GAAG,IAAI,EAAE,CAAC;gBACjF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YACzF,CAAC;QACH,CAAC;QAED,qEAAqE;QACrE,sEAAsE;QACtE,kDAAkD;QAClD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAChF,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YAChC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAAE,SAAS;YACpC,MAAM,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAClC,MAAM,CAAC,IAAI,CACT,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,EAAE,IAAI,IAAI,CAAC,MAAM,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAC5F,CAAC;QACJ,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Visao } from "../visao/projetar.js";
|
|
2
|
+
import type { Pintor } from "./cor.js";
|
|
3
|
+
/** "hoje", "1 dia", "N dias" — nunca "0 dias", que não é português. */
|
|
4
|
+
export declare function tempoAberto(aberto: string, agora: Date): string;
|
|
5
|
+
export declare function desenharBloqueios(v: Visao, colunas: number, pintar: Pintor, agora: Date): string[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { cortar } from "./largura.js";
|
|
2
|
+
/**
|
|
3
|
+
* A seção de bloqueios — a que sobe para o topo.
|
|
4
|
+
*
|
|
5
|
+
* O "há quanto tempo" é em DIAS porque `aberto_em` é `DataIso` (`AAAA-MM-DD`),
|
|
6
|
+
* sem hora (`src/parser/esquema/kinds.ts`). O rastro tem `ts` com hora no
|
|
7
|
+
* evento `task_bloqueada` e refinaria isso, ao custo de cruzar duas fontes —
|
|
8
|
+
* decisão D-14 escolheu não pagar.
|
|
9
|
+
*/
|
|
10
|
+
const MS_POR_DIA = 86_400_000;
|
|
11
|
+
/** Dias inteiros entre a data de abertura e hoje, ambos em UTC. */
|
|
12
|
+
function diasDesde(aberto, agora) {
|
|
13
|
+
const a = Date.parse(`${aberto}T00:00:00Z`);
|
|
14
|
+
if (Number.isNaN(a))
|
|
15
|
+
return 0;
|
|
16
|
+
const b = Date.UTC(agora.getUTCFullYear(), agora.getUTCMonth(), agora.getUTCDate());
|
|
17
|
+
return Math.max(0, Math.floor((b - a) / MS_POR_DIA));
|
|
18
|
+
}
|
|
19
|
+
/** "hoje", "1 dia", "N dias" — nunca "0 dias", que não é português. */
|
|
20
|
+
export function tempoAberto(aberto, agora) {
|
|
21
|
+
const d = diasDesde(aberto, agora);
|
|
22
|
+
if (d === 0)
|
|
23
|
+
return "hoje";
|
|
24
|
+
return d === 1 ? "1 dia" : `${String(d)} dias`;
|
|
25
|
+
}
|
|
26
|
+
export function desenharBloqueios(v, colunas, pintar, agora) {
|
|
27
|
+
if (v.bloqueiosAbertos.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const n = v.bloqueiosAbertos.length;
|
|
30
|
+
const titulo = n === 1 ? "1 bloqueio aberto" : `${String(n)} bloqueios abertos`;
|
|
31
|
+
const linhas = [pintar(cortar(titulo, colunas), "erro")];
|
|
32
|
+
for (const b of v.bloqueiosAbertos) {
|
|
33
|
+
const alvo = b.task ?? "sem task";
|
|
34
|
+
const quando = tempoAberto(b.aberto_em, agora);
|
|
35
|
+
// corta primeiro, pinta depois: escape ANSI não ocupa coluna
|
|
36
|
+
const texto = cortar(` ${b.id} · ${alvo} · ${quando} · ${b.descricao}`, colunas);
|
|
37
|
+
linhas.push(pintar(texto, "erro"));
|
|
38
|
+
}
|
|
39
|
+
return linhas;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=bloqueios.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bloqueios.js","sourceRoot":"","sources":["../../../src/watch/desenho/bloqueios.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;;GAOG;AAEH,MAAM,UAAU,GAAG,UAAU,CAAC;AAE9B,mEAAmE;AACnE,SAAS,SAAS,CAAC,MAAc,EAAE,KAAW;IAC5C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,EAAE,KAAK,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IACpF,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,KAAW;IACrD,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAC3B,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,CAAQ,EACR,OAAe,EACf,MAAc,EACd,KAAW;IAEX,IAAI,CAAC,CAAC,gBAAgB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAE/C,MAAM,CAAC,GAAG,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC;IACpC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC;IAChF,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;IAEzD,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,gBAAgB,EAAE,CAAC;QACnC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,UAAU,CAAC;QAClC,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC/C,6DAA6D;QAC7D,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,IAAI,MAAM,MAAM,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,OAAO,CAAC,CAAC;QAClF,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACrC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Visao } from "../visao/projetar.js";
|
|
2
|
+
import type { Pintor } from "./cor.js";
|
|
3
|
+
/**
|
|
4
|
+
* O cabeçalho: que trabalho é, em que pé está.
|
|
5
|
+
*
|
|
6
|
+
* A linha de legado (raio e orçamento) e a de repositório (branch e PR) só
|
|
7
|
+
* existem quando o `estado.json` pôde ser lido: nenhum kind do `expx-schema`
|
|
8
|
+
* declara esses campos, então no modo degradado não há de onde tirá-los
|
|
9
|
+
* (base/estado-json.md, risco 2).
|
|
10
|
+
*/
|
|
11
|
+
export declare function desenharCabecalho(v: Visao, colunas: number, pintar: Pintor): string[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { cortar } from "./largura.js";
|
|
2
|
+
/**
|
|
3
|
+
* O cabeçalho: que trabalho é, em que pé está.
|
|
4
|
+
*
|
|
5
|
+
* A linha de legado (raio e orçamento) e a de repositório (branch e PR) só
|
|
6
|
+
* existem quando o `estado.json` pôde ser lido: nenhum kind do `expx-schema`
|
|
7
|
+
* declara esses campos, então no modo degradado não há de onde tirá-los
|
|
8
|
+
* (base/estado-json.md, risco 2).
|
|
9
|
+
*/
|
|
10
|
+
export function desenharCabecalho(v, colunas, pintar) {
|
|
11
|
+
if (v.trabalho === null) {
|
|
12
|
+
return [pintar(cortar("nenhum trabalho aberto", colunas), "apagado")];
|
|
13
|
+
}
|
|
14
|
+
const t = v.trabalho;
|
|
15
|
+
const linhas = [];
|
|
16
|
+
// Linha 1: identificação. O título do plano é o completo; o `titulo_curto`
|
|
17
|
+
// do estado.json já vem cortado em 30 e serviria à barra de status, não aqui.
|
|
18
|
+
//
|
|
19
|
+
// Regra que vale para o arquivo inteiro: CORTA primeiro, PINTA depois. Os
|
|
20
|
+
// escapes ANSI não ocupam coluna, então medir texto já pintado dá largura
|
|
21
|
+
// errada — e foi o primeiro jeito que este arquivo teve.
|
|
22
|
+
const marca = t.status === "bloqueado" ? "erro" : "destaque";
|
|
23
|
+
const identificacao = cortar(`${t.trabalho_id} · ${t.expx_tool} · ${t.titulo}`, colunas);
|
|
24
|
+
linhas.push(pintar(identificacao, marca));
|
|
25
|
+
// Linha 2: progresso. O par vem do estado quando há, do plano quando não.
|
|
26
|
+
const fase = v.estado?.fase ?? t.estagio;
|
|
27
|
+
const progresso = `${String(v.concluidas)}/${String(v.total)} tasks`;
|
|
28
|
+
const task = v.estado?.task != null ? ` · agora ${v.estado.task}` : "";
|
|
29
|
+
linhas.push(cortar(` ${fase} · ${progresso}${task}`, colunas));
|
|
30
|
+
// Linha 3: modo legado. Só com estado legível E raio preenchido.
|
|
31
|
+
if (v.estado?.raio != null) {
|
|
32
|
+
const partes = [`raio ${v.estado.raio}`];
|
|
33
|
+
if (v.estado.orcamento_arquivos !== null) {
|
|
34
|
+
partes.push(`arquivos ${v.estado.orcamento_arquivos}`);
|
|
35
|
+
}
|
|
36
|
+
if (v.estado.orcamento_linhas !== null) {
|
|
37
|
+
partes.push(`linhas ${v.estado.orcamento_linhas}`);
|
|
38
|
+
}
|
|
39
|
+
const papel = v.estado.raio === "alto" ? "erro" : "atencao";
|
|
40
|
+
linhas.push(` ${pintar(cortar(partes.join(" · "), colunas - 2), papel)}`);
|
|
41
|
+
}
|
|
42
|
+
// Linha 4: repositório. Idem — sem estado, sem branch e sem PR.
|
|
43
|
+
if (v.estado?.branch != null) {
|
|
44
|
+
const pr = v.estado.pr_estado !== null ? ` · pr ${v.estado.pr_estado}` : "";
|
|
45
|
+
linhas.push(` ${pintar(cortar(`${v.estado.branch}${pr}`, colunas - 2), "apagado")}`);
|
|
46
|
+
}
|
|
47
|
+
return linhas;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=cabecalho.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cabecalho.js","sourceRoot":"","sources":["../../../src/watch/desenho/cabecalho.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cor no terminal, escrita à mão.
|
|
3
|
+
*
|
|
4
|
+
* O projeto não tem nenhuma biblioteca de cor disponível em runtime: os três
|
|
5
|
+
* pacotes ANSI em `node_modules` (`picocolors`, `ansi-styles`, `ansi-regex`)
|
|
6
|
+
* são transitivos de devDependencies e somem numa instalação de produção
|
|
7
|
+
* (base/bibliotecas-de-terminal.md). Seis códigos SGR não justificam uma
|
|
8
|
+
* dependência nova — decisão D-04.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Os papéis de cor do painel, por significado e não por cor.
|
|
12
|
+
*
|
|
13
|
+
* Nomear por papel é o que permite trocar a paleta depois sem caçar "vermelho"
|
|
14
|
+
* espalhado pelo desenho.
|
|
15
|
+
*/
|
|
16
|
+
export declare const PAPEIS: readonly ["sucesso", "atencao", "erro", "destaque", "apagado", "neutro"];
|
|
17
|
+
export type Papel = (typeof PAPEIS)[number];
|
|
18
|
+
export type Ambiente = {
|
|
19
|
+
/** `process.stdout.isTTY` — e SÓ ele. */
|
|
20
|
+
tty: boolean;
|
|
21
|
+
/** A variável `NO_COLOR` está definida? */
|
|
22
|
+
noColor: boolean;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Decide se a cor sai.
|
|
26
|
+
*
|
|
27
|
+
* Duas condições, nesta ordem: a saída precisa ser terminal (especificação:
|
|
28
|
+
* "sem cor quando a saída não for terminal, para não sujar redirecionamento de
|
|
29
|
+
* arquivo") e `NO_COLOR` não pode estar definida (decisão D-10).
|
|
30
|
+
*
|
|
31
|
+
* Repare que NÃO usamos `ehInterativo()` do CLI: aquele exige stdin E stdout, e
|
|
32
|
+
* aqui stdin é irrelevante — o que suja o arquivo é o stdout.
|
|
33
|
+
*/
|
|
34
|
+
export declare function corAtiva(amb: Ambiente): boolean;
|
|
35
|
+
/** Lê o ambiente real. Isolado para o teste nunca depender de TTY de verdade. */
|
|
36
|
+
export declare function ambienteAtual(): Ambiente;
|
|
37
|
+
export type Pintor = (texto: string, papel: Papel) => string;
|
|
38
|
+
/**
|
|
39
|
+
* Devolve a função de pintura já com a decisão tomada.
|
|
40
|
+
*
|
|
41
|
+
* Com a cor desligada, `pintar` é identidade — nenhum escape chega ao arquivo,
|
|
42
|
+
* e o desenho não precisa de um `if` em cada linha.
|
|
43
|
+
*/
|
|
44
|
+
export declare function criarPintor(ativa: boolean): Pintor;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cor no terminal, escrita à mão.
|
|
3
|
+
*
|
|
4
|
+
* O projeto não tem nenhuma biblioteca de cor disponível em runtime: os três
|
|
5
|
+
* pacotes ANSI em `node_modules` (`picocolors`, `ansi-styles`, `ansi-regex`)
|
|
6
|
+
* são transitivos de devDependencies e somem numa instalação de produção
|
|
7
|
+
* (base/bibliotecas-de-terminal.md). Seis códigos SGR não justificam uma
|
|
8
|
+
* dependência nova — decisão D-04.
|
|
9
|
+
*/
|
|
10
|
+
const ESC = "";
|
|
11
|
+
const RESET = `${ESC}[0m`;
|
|
12
|
+
/**
|
|
13
|
+
* Os papéis de cor do painel, por significado e não por cor.
|
|
14
|
+
*
|
|
15
|
+
* Nomear por papel é o que permite trocar a paleta depois sem caçar "vermelho"
|
|
16
|
+
* espalhado pelo desenho.
|
|
17
|
+
*/
|
|
18
|
+
export const PAPEIS = [
|
|
19
|
+
"sucesso",
|
|
20
|
+
"atencao",
|
|
21
|
+
"erro",
|
|
22
|
+
"destaque",
|
|
23
|
+
"apagado",
|
|
24
|
+
"neutro",
|
|
25
|
+
];
|
|
26
|
+
/** Código SGR de cada papel. */
|
|
27
|
+
const CODIGO = {
|
|
28
|
+
sucesso: "32", // verde: concluído
|
|
29
|
+
atencao: "33", // amarelo: em andamento, aviso
|
|
30
|
+
erro: "31", // vermelho: bloqueado, violação
|
|
31
|
+
destaque: "1;36", // ciano forte: a task em andamento
|
|
32
|
+
apagado: "90", // cinza: pendente, metadado
|
|
33
|
+
neutro: "0", // sem cor, mas passa pelo mesmo caminho
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Decide se a cor sai.
|
|
37
|
+
*
|
|
38
|
+
* Duas condições, nesta ordem: a saída precisa ser terminal (especificação:
|
|
39
|
+
* "sem cor quando a saída não for terminal, para não sujar redirecionamento de
|
|
40
|
+
* arquivo") e `NO_COLOR` não pode estar definida (decisão D-10).
|
|
41
|
+
*
|
|
42
|
+
* Repare que NÃO usamos `ehInterativo()` do CLI: aquele exige stdin E stdout, e
|
|
43
|
+
* aqui stdin é irrelevante — o que suja o arquivo é o stdout.
|
|
44
|
+
*/
|
|
45
|
+
export function corAtiva(amb) {
|
|
46
|
+
return amb.tty && !amb.noColor;
|
|
47
|
+
}
|
|
48
|
+
/** Lê o ambiente real. Isolado para o teste nunca depender de TTY de verdade. */
|
|
49
|
+
export function ambienteAtual() {
|
|
50
|
+
return {
|
|
51
|
+
tty: process.stdout.isTTY === true,
|
|
52
|
+
noColor: process.env["NO_COLOR"] !== undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Devolve a função de pintura já com a decisão tomada.
|
|
57
|
+
*
|
|
58
|
+
* Com a cor desligada, `pintar` é identidade — nenhum escape chega ao arquivo,
|
|
59
|
+
* e o desenho não precisa de um `if` em cada linha.
|
|
60
|
+
*/
|
|
61
|
+
export function criarPintor(ativa) {
|
|
62
|
+
if (!ativa)
|
|
63
|
+
return (texto) => texto;
|
|
64
|
+
return (texto, papel) => `${ESC}[${CODIGO[papel]}m${texto}${RESET}`;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=cor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cor.js","sourceRoot":"","sources":["../../../src/watch/desenho/cor.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,GAAG,GAAG,GAAG,CAAC;AAChB,MAAM,KAAK,GAAG,GAAG,GAAG,KAAK,CAAC;AAE1B;;;;;GAKG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,SAAS;IACT,SAAS;IACT,MAAM;IACN,UAAU;IACV,SAAS;IACT,QAAQ;CACA,CAAC;AAIX,gCAAgC;AAChC,MAAM,MAAM,GAA0B;IACpC,OAAO,EAAE,IAAI,EAAE,mBAAmB;IAClC,OAAO,EAAE,IAAI,EAAE,+BAA+B;IAC9C,IAAI,EAAE,IAAI,EAAE,gCAAgC;IAC5C,QAAQ,EAAE,MAAM,EAAE,mCAAmC;IACrD,OAAO,EAAE,IAAI,EAAE,4BAA4B;IAC3C,MAAM,EAAE,GAAG,EAAE,wCAAwC;CACtD,CAAC;AASF;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,GAAa;IACpC,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;AACjC,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa;IAC3B,OAAO;QACL,GAAG,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;QAClC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,SAAS;KAC/C,CAAC;AACJ,CAAC;AAID;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC;IACpC,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,KAAK,EAAE,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Visao } from "../visao/projetar.js";
|
|
2
|
+
/**
|
|
3
|
+
* O painel inteiro, como lista de linhas.
|
|
4
|
+
*
|
|
5
|
+
* Função PURA: recebe a visão, a largura e se há cor, e devolve texto. Não
|
|
6
|
+
* toca em `process.stdout`, não lê TTY, não escreve em disco. É o que torna
|
|
7
|
+
* possível testar as dez fixtures da especificação sem nenhum terminal — as
|
|
8
|
+
* duas últimas (60 colunas, saída redirecionada) são justamente os parâmetros
|
|
9
|
+
* `colunas` e `cor` (decisão D-11).
|
|
10
|
+
*/
|
|
11
|
+
export type OpcoesDesenho = {
|
|
12
|
+
colunas: number;
|
|
13
|
+
cor: boolean;
|
|
14
|
+
agora: Date;
|
|
15
|
+
};
|
|
16
|
+
export declare function desenhar(v: Visao, op: OpcoesDesenho): string[];
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { desenharArvore } from "./arvore.js";
|
|
2
|
+
import { desenharBloqueios } from "./bloqueios.js";
|
|
3
|
+
import { desenharCabecalho } from "./cabecalho.js";
|
|
4
|
+
import { criarPintor } from "./cor.js";
|
|
5
|
+
import { desenharEventos } from "./eventos.js";
|
|
6
|
+
import { cortar } from "./largura.js";
|
|
7
|
+
const MINUTO = 60_000;
|
|
8
|
+
const HORA = 60 * MINUTO;
|
|
9
|
+
const DIA = 24 * HORA;
|
|
10
|
+
/** "há 3 min", "há 2 h", "há 4 d" — curto, porque é rodapé. */
|
|
11
|
+
function desde(quando, agora) {
|
|
12
|
+
const ms = Math.max(0, agora.getTime() - quando.getTime());
|
|
13
|
+
if (ms < MINUTO)
|
|
14
|
+
return "há instantes";
|
|
15
|
+
if (ms < HORA)
|
|
16
|
+
return `há ${String(Math.floor(ms / MINUTO))} min`;
|
|
17
|
+
if (ms < DIA)
|
|
18
|
+
return `há ${String(Math.floor(ms / HORA))} h`;
|
|
19
|
+
return `há ${String(Math.floor(ms / DIA))} d`;
|
|
20
|
+
}
|
|
21
|
+
function desenharRodape(v, colunas, pintar, agora) {
|
|
22
|
+
const partes = [];
|
|
23
|
+
// Tempo desde o último evento: "se passar muito, a execução travou".
|
|
24
|
+
const ultimo = v.eventos[0];
|
|
25
|
+
if (ultimo !== undefined) {
|
|
26
|
+
partes.push(`ultimo evento ${desde(new Date(ultimo.ts), agora)}`);
|
|
27
|
+
}
|
|
28
|
+
// Violações em modo aviso acumuladas na sessão (D-07).
|
|
29
|
+
if (v.violacoesAviso > 0) {
|
|
30
|
+
const n = v.violacoesAviso;
|
|
31
|
+
partes.push(n === 1 ? "1 violacao em aviso" : `${String(n)} violacoes em aviso`);
|
|
32
|
+
}
|
|
33
|
+
if (partes.length === 0)
|
|
34
|
+
return [];
|
|
35
|
+
const papel = v.violacoesAviso > 0 ? "atencao" : "apagado";
|
|
36
|
+
return [pintar(cortar(partes.join(" · "), colunas), papel)];
|
|
37
|
+
}
|
|
38
|
+
export function desenhar(v, op) {
|
|
39
|
+
const pintar = criarPintor(op.cor);
|
|
40
|
+
const c = op.colunas;
|
|
41
|
+
// A ordem é a prioridade da especificação: bloqueio aberto sobe para o topo,
|
|
42
|
+
// acima até do cabeçalho, "porque é a informação que mais precisa de olho
|
|
43
|
+
// humano e a que mais passa despercebida quando a execução é autônoma".
|
|
44
|
+
const secoes = [
|
|
45
|
+
desenharBloqueios(v, c, pintar, op.agora),
|
|
46
|
+
desenharCabecalho(v, c, pintar),
|
|
47
|
+
desenharArvore(v, c, pintar),
|
|
48
|
+
desenharEventos(v, c, pintar),
|
|
49
|
+
desenharRodape(v, c, pintar, op.agora),
|
|
50
|
+
].filter((s) => s.length > 0);
|
|
51
|
+
// Uma linha em branco entre seções, nenhuma no fim.
|
|
52
|
+
return secoes.flatMap((s, i) => (i === 0 ? s : ["", ...s]));
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=desenhar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"desenhar.js","sourceRoot":"","sources":["../../../src/watch/desenho/desenhar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { cortar } from "./largura.js";
|
|
2
|
+
/**
|
|
3
|
+
* Os eventos recentes, do rastro, em ordem inversa.
|
|
4
|
+
*
|
|
5
|
+
* O `agente` sai em toda linha porque é o que responde "quem fez o quê" —
|
|
6
|
+
* e no contrato ele nunca é `null`: `principal` é o valor quando não há
|
|
7
|
+
* subagente, e isso é informação, não ausência.
|
|
8
|
+
*/
|
|
9
|
+
/** Só a hora, do `ts` que é `AAAA-MM-DDTHH:MM:SSZ`. */
|
|
10
|
+
function hora(ts) {
|
|
11
|
+
return ts.slice(11, 16);
|
|
12
|
+
}
|
|
13
|
+
/** O papel de cor de cada evento, pelo que ele significa. */
|
|
14
|
+
function papelDe(e) {
|
|
15
|
+
if (e.evento === "regra_violada" || e.evento === "acao_bloqueada")
|
|
16
|
+
return "erro";
|
|
17
|
+
if (e.evento === "task_bloqueada")
|
|
18
|
+
return "erro";
|
|
19
|
+
if (e.evento === "task_concluida" || e.evento === "fase_concluida")
|
|
20
|
+
return "sucesso";
|
|
21
|
+
if (e.resultado !== "ok")
|
|
22
|
+
return "atencao";
|
|
23
|
+
return "apagado";
|
|
24
|
+
}
|
|
25
|
+
export function desenharEventos(v, colunas, pintar) {
|
|
26
|
+
if (v.eventos.length === 0)
|
|
27
|
+
return [];
|
|
28
|
+
const linhas = [pintar(cortar("eventos recentes", colunas), "apagado")];
|
|
29
|
+
for (const e of v.eventos) {
|
|
30
|
+
const alvo = e.task ?? e.fase ?? "";
|
|
31
|
+
const detalhe = e.detalhe !== "" ? ` · ${e.detalhe}` : "";
|
|
32
|
+
const texto = ` ${hora(e.ts)} ${e.evento} ${alvo} [${e.agente}]${detalhe}`;
|
|
33
|
+
linhas.push(pintar(cortar(texto, colunas), papelDe(e)));
|
|
34
|
+
}
|
|
35
|
+
return linhas;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=eventos.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eventos.js","sourceRoot":"","sources":["../../../src/watch/desenho/eventos.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;GAMG;AAEH,uDAAuD;AACvD,SAAS,IAAI,CAAC,EAAU;IACtB,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AAC1B,CAAC;AAED,6DAA6D;AAC7D,SAAS,OAAO,CAAC,CAAc;IAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,eAAe,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO,MAAM,CAAC;IACjF,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO,MAAM,CAAC;IACjD,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB,IAAI,CAAC,CAAC,MAAM,KAAK,gBAAgB;QAAE,OAAO,SAAS,CAAC;IACrF,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,CAAQ,EAAE,OAAe,EAAE,MAAc;IACvE,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEtC,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,kBAAkB,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAExE,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;QACpC,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1D,MAAM,KAAK,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,IAAI,IAAI,KAAK,CAAC,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC;QAC5E,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Medir e cortar texto por COLUNA de terminal.
|
|
3
|
+
*
|
|
4
|
+
* R13 (`CONVENCOES.md`) garante acento em `titulo`, `objetivo` e `detalhe`. Em
|
|
5
|
+
* NFC o "ç" é um code point; em NFD são dois — e nome vindo do macOS chega em
|
|
6
|
+
* NFD. Medir com `String.length` erraria o corte em 80 colunas (decisão D-15).
|
|
7
|
+
*
|
|
8
|
+
* Normalizar para NFC resolve o caso real do método. Não tratamos largura
|
|
9
|
+
* dupla (CJK, emoji): a especificação proíbe emoji decorativo e a prosa do
|
|
10
|
+
* método é português. Um caractere de largura dupla contaria como uma coluna
|
|
11
|
+
* aqui — limitação conhecida, não acidente.
|
|
12
|
+
*/
|
|
13
|
+
/** Quantas colunas o texto ocupa. */
|
|
14
|
+
export declare function largura(texto: string): number;
|
|
15
|
+
/**
|
|
16
|
+
* Corta o texto para caber em `colunas`, com reticência quando sobra coisa.
|
|
17
|
+
*
|
|
18
|
+
* "Corta com elegância em vez de quebrar linha", como manda a especificação:
|
|
19
|
+
* quem lê num terminal estreito perde o fim da frase, nunca o alinhamento.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cortar(texto: string, colunas: number): string;
|
|
22
|
+
/** Completa com espaços à direita até `colunas`, cortando se passar. */
|
|
23
|
+
export declare function preencher(texto: string, colunas: number): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Medir e cortar texto por COLUNA de terminal.
|
|
3
|
+
*
|
|
4
|
+
* R13 (`CONVENCOES.md`) garante acento em `titulo`, `objetivo` e `detalhe`. Em
|
|
5
|
+
* NFC o "ç" é um code point; em NFD são dois — e nome vindo do macOS chega em
|
|
6
|
+
* NFD. Medir com `String.length` erraria o corte em 80 colunas (decisão D-15).
|
|
7
|
+
*
|
|
8
|
+
* Normalizar para NFC resolve o caso real do método. Não tratamos largura
|
|
9
|
+
* dupla (CJK, emoji): a especificação proíbe emoji decorativo e a prosa do
|
|
10
|
+
* método é português. Um caractere de largura dupla contaria como uma coluna
|
|
11
|
+
* aqui — limitação conhecida, não acidente.
|
|
12
|
+
*/
|
|
13
|
+
/** Reticência de uma coluna, para marcar o que foi cortado. */
|
|
14
|
+
const RETICENCIA = "…";
|
|
15
|
+
/** Quantas colunas o texto ocupa. */
|
|
16
|
+
export function largura(texto) {
|
|
17
|
+
// O spread itera por code point, não por unidade UTF-16: um par surrogate
|
|
18
|
+
// conta como um, e não como dois.
|
|
19
|
+
return [...texto.normalize("NFC")].length;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Corta o texto para caber em `colunas`, com reticência quando sobra coisa.
|
|
23
|
+
*
|
|
24
|
+
* "Corta com elegância em vez de quebrar linha", como manda a especificação:
|
|
25
|
+
* quem lê num terminal estreito perde o fim da frase, nunca o alinhamento.
|
|
26
|
+
*/
|
|
27
|
+
export function cortar(texto, colunas) {
|
|
28
|
+
if (colunas <= 0)
|
|
29
|
+
return "";
|
|
30
|
+
const normalizado = texto.normalize("NFC");
|
|
31
|
+
const pontos = [...normalizado];
|
|
32
|
+
if (pontos.length <= colunas)
|
|
33
|
+
return normalizado;
|
|
34
|
+
// -1 para a reticência, que também ocupa uma coluna
|
|
35
|
+
return pontos.slice(0, colunas - 1).join("") + RETICENCIA;
|
|
36
|
+
}
|
|
37
|
+
/** Completa com espaços à direita até `colunas`, cortando se passar. */
|
|
38
|
+
export function preencher(texto, colunas) {
|
|
39
|
+
const cortado = cortar(texto, colunas);
|
|
40
|
+
return cortado + " ".repeat(Math.max(0, colunas - largura(cortado)));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=largura.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"largura.js","sourceRoot":"","sources":["../../../src/watch/desenho/largura.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,+DAA+D;AAC/D,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,qCAAqC;AACrC,MAAM,UAAU,OAAO,CAAC,KAAa;IACnC,0EAA0E;IAC1E,kCAAkC;IAClC,OAAO,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AAC5C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,OAAe;IACnD,IAAI,OAAO,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAE5B,MAAM,WAAW,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC;IAChC,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO;QAAE,OAAO,WAAW,CAAC;IAEjD,oDAAoD;IACpD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC;AAC5D,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,OAAe;IACtD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACvC,OAAO,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Visao } from "../visao/projetar.js";
|
|
2
|
+
/**
|
|
3
|
+
* `expx watch --todos`: os trabalhos abertos, um por linha, sem a árvore.
|
|
4
|
+
*
|
|
5
|
+
* "Útil para quem tem várias ocorrências em paralelo." Aberto é todo status
|
|
6
|
+
* diferente de `concluido` (decisão D-16): `bloqueado` e `nao_iniciado` são
|
|
7
|
+
* exatamente o que a pessoa precisa ver numa lista de acompanhamento.
|
|
8
|
+
*/
|
|
9
|
+
export declare function desenharLista(v: Visao, colunas: number, cor: boolean): string[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { criarPintor } from "./cor.js";
|
|
2
|
+
import { cortar } from "./largura.js";
|
|
3
|
+
/**
|
|
4
|
+
* `expx watch --todos`: os trabalhos abertos, um por linha, sem a árvore.
|
|
5
|
+
*
|
|
6
|
+
* "Útil para quem tem várias ocorrências em paralelo." Aberto é todo status
|
|
7
|
+
* diferente de `concluido` (decisão D-16): `bloqueado` e `nao_iniciado` são
|
|
8
|
+
* exatamente o que a pessoa precisa ver numa lista de acompanhamento.
|
|
9
|
+
*/
|
|
10
|
+
export function desenharLista(v, colunas, cor) {
|
|
11
|
+
const pintar = criarPintor(cor);
|
|
12
|
+
if (v.abertos.length === 0) {
|
|
13
|
+
return [pintar(cortar("nenhum trabalho aberto", colunas), "apagado")];
|
|
14
|
+
}
|
|
15
|
+
const linhas = [pintar(cortar(`${String(v.abertos.length)} trabalhos abertos`, colunas), "apagado")];
|
|
16
|
+
for (const t of v.abertos) {
|
|
17
|
+
const tasks = t.sprints.flatMap((s) => s.tasks);
|
|
18
|
+
const feitas = tasks.filter((x) => x.status === "concluida").length;
|
|
19
|
+
const progresso = tasks.length > 0 ? ` · ${String(feitas)}/${String(tasks.length)}` : "";
|
|
20
|
+
const bloqueios = t.bloqueios.filter((b) => b.aberto).length;
|
|
21
|
+
const marcaBloqueio = bloqueios > 0 ? ` · ${String(bloqueios)} bloq` : "";
|
|
22
|
+
const papel = t.status === "bloqueado" ? "erro" : t.status === "em_andamento" ? "atencao" : "apagado";
|
|
23
|
+
const texto = ` ${t.trabalho_id} · ${t.expx_tool} · ${t.status}${progresso}${marcaBloqueio} · ${t.titulo}`;
|
|
24
|
+
linhas.push(pintar(cortar(texto, colunas), papel));
|
|
25
|
+
}
|
|
26
|
+
return linhas;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=lista.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lista.js","sourceRoot":"","sources":["../../../src/watch/desenho/lista.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,CAAQ,EAAE,OAAe,EAAE,GAAY;IACnE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEhC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,wBAAwB,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;IAErG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,MAAM,CAAC;QACpE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzF,MAAM,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;QAC7D,MAAM,aAAa,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QAE1E,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC;QACtG,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,MAAM,GAAG,SAAS,GAAG,aAAa,MAAM,CAAC,CAAC,MAAM,EAAE,CAAC;QAC5G,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lê apenas o fim de um arquivo de texto, em linhas completas.
|
|
3
|
+
*
|
|
4
|
+
* O rastro só rotaciona em 5 MB (contrato `expx-eventos`), e o watch relê a
|
|
5
|
+
* cada evento novo. Ler o arquivo inteiro todas as vezes é a "chamada externa
|
|
6
|
+
* cara" que a especificação proíbe — daí a decisão D-09 de ler só a cauda.
|
|
7
|
+
*
|
|
8
|
+
* O preço de ler pelo fim é cortar a primeira linha ao meio. Este módulo
|
|
9
|
+
* DESCARTA esse fragmento: quem consome recebe só linhas íntegras, e uma linha
|
|
10
|
+
* perdida no começo da janela é irrelevante para "as últimas N linhas".
|
|
11
|
+
*/
|
|
12
|
+
/** Teto de bytes lidos por arquivo. */
|
|
13
|
+
export declare const LIMITE_CAUDA: number;
|
|
14
|
+
export declare function lerCauda(caminho: string, limite?: number): string[];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync, statSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Lê apenas o fim de um arquivo de texto, em linhas completas.
|
|
4
|
+
*
|
|
5
|
+
* O rastro só rotaciona em 5 MB (contrato `expx-eventos`), e o watch relê a
|
|
6
|
+
* cada evento novo. Ler o arquivo inteiro todas as vezes é a "chamada externa
|
|
7
|
+
* cara" que a especificação proíbe — daí a decisão D-09 de ler só a cauda.
|
|
8
|
+
*
|
|
9
|
+
* O preço de ler pelo fim é cortar a primeira linha ao meio. Este módulo
|
|
10
|
+
* DESCARTA esse fragmento: quem consome recebe só linhas íntegras, e uma linha
|
|
11
|
+
* perdida no começo da janela é irrelevante para "as últimas N linhas".
|
|
12
|
+
*/
|
|
13
|
+
/** Teto de bytes lidos por arquivo. */
|
|
14
|
+
export const LIMITE_CAUDA = 64 * 1024;
|
|
15
|
+
export function lerCauda(caminho, limite = LIMITE_CAUDA) {
|
|
16
|
+
let fd;
|
|
17
|
+
try {
|
|
18
|
+
const tamanho = statSync(caminho).size;
|
|
19
|
+
if (tamanho === 0)
|
|
20
|
+
return [];
|
|
21
|
+
const inicio = Math.max(0, tamanho - limite);
|
|
22
|
+
const bytes = tamanho - inicio;
|
|
23
|
+
fd = openSync(caminho, "r");
|
|
24
|
+
const buf = Buffer.allocUnsafe(bytes);
|
|
25
|
+
readSync(fd, buf, 0, bytes, inicio);
|
|
26
|
+
const texto = buf.toString("utf8");
|
|
27
|
+
const linhas = texto.split("\n");
|
|
28
|
+
// Cortamos no meio do arquivo? Então a primeira linha é um fragmento do
|
|
29
|
+
// que veio antes da janela, e não um registro. Fora.
|
|
30
|
+
if (inicio > 0)
|
|
31
|
+
linhas.shift();
|
|
32
|
+
return linhas.filter((l) => l.trim() !== "");
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// arquivo ausente, sem permissão, sumiu entre o stat e o open: sem rastro
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
if (fd !== undefined) {
|
|
40
|
+
try {
|
|
41
|
+
closeSync(fd);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// fechar não pode derrubar quem só queria ler
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=cauda.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cauda.js","sourceRoot":"","sources":["../../../src/watch/fontes/cauda.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAElE;;;;;;;;;;GAUG;AAEH,uCAAuC;AACvC,MAAM,CAAC,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,CAAC;AAEtC,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,SAAiB,YAAY;IACrE,IAAI,EAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC;QACvC,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAE7B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,OAAO,GAAG,MAAM,CAAC;QAE/B,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC5B,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QACtC,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QAEpC,MAAM,KAAK,GAAG,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QACnC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEjC,wEAAwE;QACxE,qDAAqD;QACrD,IAAI,MAAM,GAAG,CAAC;YAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QAE/B,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,0EAA0E;QAC1E,OAAO,EAAE,CAAC;IACZ,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,IAAI,CAAC;gBACH,SAAS,CAAC,EAAE,CAAC,CAAC;YAChB,CAAC;YAAC,MAAM,CAAC;gBACP,8CAA8C;YAChD,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
|