diff --git a/src/lib/components/SectionTaView.svelte b/src/lib/components/SectionTaView.svelte index 442cab8..1f3cffe 100644 --- a/src/lib/components/SectionTaView.svelte +++ b/src/lib/components/SectionTaView.svelte @@ -24,13 +24,13 @@
{#if showArticles} - {#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA.LIEN_ART)] as lienArt} + {#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA?.LIEN_ART)] as lienArt} {@const article = data.article?.[lienArt["@id"]]} {#if article !== undefined} {/if} {/each} - {:else if sectionTa.STRUCTURE_TA.LIEN_ART !== undefined} + {:else if sectionTa.STRUCTURE_TA?.LIEN_ART !== undefined} {/if} - {#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA.LIEN_SECTION_TA)] as lienSectionTa} + {#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA?.LIEN_SECTION_TA)] as lienSectionTa} {@const sectionTa = data.section_ta?.[lienSectionTa["@id"]]} {#if sectionTa !== undefined} diff --git a/src/lib/legal/index.ts b/src/lib/legal/index.ts index 4db364a..5e00edc 100644 --- a/src/lib/legal/index.ts +++ b/src/lib/legal/index.ts @@ -418,7 +418,7 @@ export interface SectionTa { ID: string CONTEXTE: Contexte TITRE_TA: string - STRUCTURE_TA: { + STRUCTURE_TA?: { LIEN_ART?: LienArt | LienArt[] LIEN_SECTION_TA?: LienSectionTa | LienSectionTa[] } diff --git a/src/lib/server/aggregates.ts b/src/lib/server/aggregates.ts index 68d7c9e..1bf2893 100644 --- a/src/lib/server/aggregates.ts +++ b/src/lib/server/aggregates.ts @@ -66,7 +66,7 @@ export class Aggregator { if (this.follow.has("STRUCTURE_TA.LIEN_ART.@id")) { for (const lien of iterArrayOrSingleton( - sectionTa.STRUCTURE_TA.LIEN_ART, + sectionTa.STRUCTURE_TA?.LIEN_ART, )) { this.requestId(lien["@id"]) } @@ -74,7 +74,7 @@ export class Aggregator { if (this.follow.has("STRUCTURE_TA.LIEN_SECTION_TA.@id")) { for (const lien of iterArrayOrSingleton( - sectionTa.STRUCTURE_TA.LIEN_SECTION_TA, + sectionTa.STRUCTURE_TA?.LIEN_SECTION_TA, )) { this.requestId(lien["@id"]) } diff --git a/src/lib/summaries.ts b/src/lib/summaries.ts index 1cce0b8..afb6572 100644 --- a/src/lib/summaries.ts +++ b/src/lib/summaries.ts @@ -29,6 +29,7 @@ import { type TmLienTxt, type VersionsWrapper, } from "$lib/legal" +import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared" export const summarizeAggregateProperties: Summarizer = (access, value) => { for (const summarizer of [ @@ -49,6 +50,69 @@ export const summarizeAggregateProperties: Summarizer = (access, value) => { return undefined } +export const summarizeArticleLienDb: Summarizer = (access, value) => { + const articleLien = value as ArticleLienDb | undefined + if (articleLien === undefined) { + return undefined + } + return { + items: [ + { + content: `Article ${articleLien.article_id}`, + href: pathnameFromLegalId(articleLien.article_id) as string, + type: "link", + }, + ` ${articleLien.typelien} ${articleLien.cible ? "CIBLE" : "SOURCE"} `, + articleLien.cidtexte === null + ? `cidtexte : null` + : { + content: `cidtexte : ${articleLien.cidtexte}`, + href: pathnameFromLegalId(articleLien.cidtexte) as string, + type: "link", + }, + ", id : ", + { + content: articleLien.id, + href: pathnameFromLegalId(articleLien.id) as string, + type: "link", + }, + ], + type: "concatenation", + } +} + +export const summarizeArticleLienDbProperties: Summarizer = (access, value) => { + if ( + access?.key === "article_lien" && + (value === undefined || + // Ensure that value is not a dictionary of article by id. + Object.keys(value as { [key: string]: unknown }).some( + (key) => key.match(/^[A-Z]{8}\d{12}$/) === null, + )) + ) { + return summarizeArticleLienDb(access, value) + } + if ( + access?.access?.key === "article_lien" && + (Array.isArray(access.parent) || + (typeof access?.key === "string" && + access.key.match(/^[A-Z]{8}\d{12}$/) !== null)) + ) { + return summarizeArticleLienDb(access, value) + } + + if (access?.key === "article_id") { + return summarizeLegalIdToLink(access, value) + } + if (access?.key === "cidtexte") { + return summarizeLegalIdToLink(access, value) + } + if (access?.key === "id") { + return summarizeLegalIdToLink(access, value) + } + return undefined +} + export const summarizeArticleProperties: Summarizer = (access, value) => { if ( access?.key === "article" && @@ -317,7 +381,7 @@ export function summarizeLegalIdToValueArrowLink( content: JSON.stringify(value), type: "raw_data", }, - { class: "mx-1 mt-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, uidLinkSummary, ], type: "concatenation", @@ -472,7 +536,7 @@ export const summarizeLienArtId: Summarizer = (access, value) => { content: JSON.stringify(lienArt["@id"]), type: "raw_data", }, - { class: "mt-1 mx-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, summarizeLienArt(access, lienArt) as Summary, ], type: "concatenation", @@ -494,7 +558,7 @@ export const summarizeLienId: Summarizer = (access, value) => { content: JSON.stringify(lien["@id"]), type: "raw_data", }, - { class: "mt-1 mx-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, { content: lien["#text"], href: pathname, @@ -528,7 +592,7 @@ export const summarizeLienSectionTaId: Summarizer = (access, value) => { content: JSON.stringify(lienSectionTa["@id"]), type: "raw_data", }, - { class: "mt-1 mx-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, summarizeLienSectionTa(access, lienSectionTa) as Summary, ], type: "concatenation", @@ -708,6 +772,72 @@ export const summarizeTextelrProperties: Summarizer = (access, value) => { return undefined } +export const summarizeTexteVersionLienDb: Summarizer = (access, value) => { + const texteVersionLien = value as TexteVersionLienDb | undefined + if (texteVersionLien === undefined) { + return undefined + } + return { + items: [ + { + content: `TexteVersion ${texteVersionLien.texte_version_id}`, + href: pathnameFromLegalId(texteVersionLien.texte_version_id) as string, + type: "link", + }, + ` ${texteVersionLien.typelien} ${texteVersionLien.cible ? "CIBLE" : "SOURCE"} `, + texteVersionLien.cidtexte === null + ? `cidtexte : null` + : { + content: `cidtexte : ${texteVersionLien.cidtexte}`, + href: pathnameFromLegalId(texteVersionLien.cidtexte) as string, + type: "link", + }, + ", id : ", + { + content: texteVersionLien.id, + href: pathnameFromLegalId(texteVersionLien.id) as string, + type: "link", + }, + ], + type: "concatenation", + } +} + +export const summarizeTexteVersionLienDbProperties: Summarizer = ( + access, + value, +) => { + if ( + access?.key === "texte_version_lien" && + (value === undefined || + // Ensure that value is not a dictionary of TexteVersion by id. + Object.keys(value as { [key: string]: unknown }).some( + (key) => key.match(/^[A-Z]{8}\d{12}$/) === null, + )) + ) { + return summarizeTexteVersionLienDb(access, value) + } + if ( + access?.access?.key === "texte_version_lien" && + (Array.isArray(access.parent) || + (typeof access?.key === "string" && + access.key.match(/^[A-Z]{8}\d{12}$/) !== null)) + ) { + return summarizeTexteVersionLienDb(access, value) + } + + if (access?.key === "texte_version_id") { + return summarizeLegalIdToLink(access, value) + } + if (access?.key === "cidtexte") { + return summarizeLegalIdToLink(access, value) + } + if (access?.key === "id") { + return summarizeLegalIdToLink(access, value) + } + return undefined +} + export const summarizeTexteVersionProperties: Summarizer = (access, value) => { if ( access?.key === "texte_version" && @@ -754,7 +884,7 @@ export const summarizeTitreTmId: Summarizer = (access, value) => { content: JSON.stringify(titreTm["@id"]), type: "raw_data", }, - { class: "mt-1 mx-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, { content: titreTm["#text"], href: pathname, @@ -780,7 +910,7 @@ export const summarizeTitreTxtIdTxt: Summarizer = (access, value) => { content: JSON.stringify(titreTxt["@id_txt"]), type: "raw_data", }, - { class: "mt-1 mx-1", icon: arrowRight, inline: true, type: "icon" }, + { class: "mr-1 mt-0.5 w-3 flex-none", icon: MoveRight, type: "icon" }, { content: titreTxt["#text"], href: pathname, diff --git a/src/routes/article/[id]/+page.server.ts b/src/routes/article/[id]/+page.server.ts index c791995..34764ec 100644 --- a/src/routes/article/[id]/+page.server.ts +++ b/src/routes/article/[id]/+page.server.ts @@ -1,6 +1,7 @@ import { error } from "@sveltejs/kit" import type { Article } from "$lib/legal" +import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared" import { db } from "$lib/server/databases" import type { PageServerLoad } from "./$types" @@ -16,5 +17,17 @@ export const load: PageServerLoad = async ({ params }) => { if (article === undefined) { error(404, `Article ${params.id} non trouvé`) } - return { article } + + const articleLienDbArray = await db` + SELECT * FROM article_lien WHERE id = ${params.id} + ` + const texteVersionLienDbArray = await db` + SELECT * FROM texte_version_lien WHERE id = ${params.id} + ` + + return { + article, + article_lien: articleLienDbArray, + texte_version_lien: texteVersionLienDbArray, + } } diff --git a/src/routes/article/[id]/+page.svelte b/src/routes/article/[id]/+page.svelte index ef2e5dd..11810c2 100644 --- a/src/routes/article/[id]/+page.svelte +++ b/src/routes/article/[id]/+page.svelte @@ -2,20 +2,26 @@ import { TreeView, SummaryView } from "augmented-data-viewer" import { + summarizeArticleLienDbProperties, summarizeArticleProperties, summarizeLegalObject, + summarizeTexteVersionLienDbProperties, } from "$lib/summaries" import type { PageData } from "./$types" export let data: PageData - $: ({ article } = data) + $: ({ + article, + article_lien: articleLienDbArray, + texte_version_lien: texteVersionLienDbArray, + } = data) $: summary = summarizeLegalObject({ key: "article" }, "article", article) -
+

@@ -35,3 +41,35 @@ summarize={summarizeArticleProperties} value={article} /> + +
+

+ Articles pointant sur l'article +

+ + +
+ +
+

+ Textes pointant sur l'article +

+ + +
diff --git a/src/routes/section_ta/[id]/+page.server.ts b/src/routes/section_ta/[id]/+page.server.ts index b4b075e..3d40073 100644 --- a/src/routes/section_ta/[id]/+page.server.ts +++ b/src/routes/section_ta/[id]/+page.server.ts @@ -1,6 +1,7 @@ import { error } from "@sveltejs/kit" import type { SectionTa } from "$lib/legal" +import type { ArticleLienDb, TexteVersionLienDb } from "$lib/legal/shared" import { db } from "$lib/server/databases" import type { PageServerLoad } from "./$types" @@ -16,5 +17,17 @@ export const load: PageServerLoad = async ({ params }) => { if (sectionTa === undefined) { error(404, `SECTION_TA ${params.id} non trouvé`) } - return { section_ta: sectionTa } + + const articleLienDbArray = await db` + SELECT * FROM article_lien WHERE id = ${params.id} + ` + const texteVersionLienDbArray = await db` + SELECT * FROM texte_version_lien WHERE id = ${params.id} + ` + + return { + article_lien: articleLienDbArray, + section_ta: sectionTa, + texte_version_lien: texteVersionLienDbArray, + } } diff --git a/src/routes/section_ta/[id]/+page.svelte b/src/routes/section_ta/[id]/+page.svelte index 7abf62f..ee07d9d 100644 --- a/src/routes/section_ta/[id]/+page.svelte +++ b/src/routes/section_ta/[id]/+page.svelte @@ -2,15 +2,21 @@ import { TreeView, SummaryView } from "augmented-data-viewer" import { - summarizeSectionTaProperties, + summarizeArticleLienDbProperties, summarizeLegalObject, + summarizeSectionTaProperties, + summarizeTexteVersionLienDbProperties, } from "$lib/summaries" import type { PageData } from "./$types" export let data: PageData - $: ({ section_ta: sectionTa } = data) + $: ({ + article_lien: articleLienDbArray, + section_ta: sectionTa, + texte_version_lien: texteVersionLienDbArray, + } = data) $: summary = summarizeLegalObject( { key: "section_ta" }, @@ -19,7 +25,7 @@ ) -
+

@@ -39,3 +45,35 @@ summarize={summarizeSectionTaProperties} value={sectionTa} /> + +
+

+ Articles pointant sur la SECTION_TA +

+ + +
+ +
+

+ Textes pointant sur la SECTION_TA +

+ + +
diff --git a/src/scripts/export_legi_textes_by_textes_modificateurs_to_git_markdown.ts b/src/scripts/export_legi_textes_by_textes_modificateurs_to_git_markdown.ts index fd43379..a26dd95 100644 --- a/src/scripts/export_legi_textes_by_textes_modificateurs_to_git_markdown.ts +++ b/src/scripts/export_legi_textes_by_textes_modificateurs_to_git_markdown.ts @@ -176,7 +176,438 @@ async function addTexteModificateurId( } } -async function exportLegiArticle( +async function exportLegiTexteToMarkdown( + legiTexteId: string, + targetDir: string, +): Promise { + const context: Context = { + articleById: {}, + currentInternalIds: new Set(), + infosArticleModificateurById: {}, + infosTexteModificateurById: {}, + legiTexteInternalIds: new Set(), + sectionTaById: {}, + targetDir, + texteManquantById: {}, + texteVersionById: {}, + } + context.legiTexteInternalIds.add(legiTexteId) + + const texteVersion = (await getOrLoadTexteVersion(context, legiTexteId)) as + | JorfTexteVersion + | LegiTexteVersion + assert.notStrictEqual(texteVersion, null) + + const textelr = ( + await db<{ data: LegiTextelr }[]>` + SELECT data FROM textelr WHERE id = ${legiTexteId} + ` + )[0]?.data + assert.notStrictEqual(textelr, undefined) + + const meta = texteVersion.META + const metaTexteVersion = meta.META_SPEC.META_TEXTE_VERSION + console.log( + `${meta.META_COMMUN.ID} ${metaTexteVersion.TITREFULL ?? metaTexteVersion.TITRE ?? meta.META_COMMUN.ID} (${metaTexteVersion.DATE_DEBUT ?? ""} — ${metaTexteVersion.DATE_FIN === "2999-01-01" ? "…" : (metaTexteVersion.DATE_FIN ?? "")}, ${metaTexteVersion.ETAT})`, + ) + + // First Pass: Register IDs of internal objects + + const textelrStructure = textelr.STRUCT + const liensArticles = textelrStructure?.LIEN_ART + if (liensArticles !== undefined) { + for (const lienArticle of liensArticles) { + context.legiTexteInternalIds.add(lienArticle["@id"]) + } + } + + for await (const { sectionTa } of walkStructureTree( + context, + textelrStructure as LegiSectionTaStructure, + )) { + const liensArticles = sectionTa?.STRUCTURE_TA?.LIEN_ART + if (liensArticles !== undefined) { + for (const lienArticle of liensArticles) { + context.legiTexteInternalIds.add(lienArticle["@id"]) + } + } + } + + // Second Pass : Register texts & articles that modify parts (aka SectionTA & Article) of the code. + + if (liensArticles !== undefined) { + for (const lienArticle of liensArticles) { + const article = (await getOrLoadArticle( + context, + lienArticle["@id"], + )) as LegiArticle + await registerLegiArticleModifiers( + context, + legiTexteId, + 0, + lienArticle, + article, + ) + } + } + + for await (const { + lienSectionTa, + parentsSectionTa, + sectionTa, + } of walkStructureTree(context, textelrStructure as LegiSectionTaStructure)) { + await registerLegiSectionTaModifiers( + context, + legiTexteId, + parentsSectionTa.length + 1, + lienSectionTa, + sectionTa, + ) + + const liensArticles = sectionTa?.STRUCTURE_TA?.LIEN_ART + if (liensArticles !== undefined) { + for (const lienArticle of liensArticles) { + const article = (await getOrLoadArticle( + context, + lienArticle["@id"], + )) as LegiArticle + await registerLegiArticleModifiers( + context, + legiTexteId, + parentsSectionTa.length + 2, + lienArticle, + article, + ) + } + } + } + + // Sort of textes modificateurs by date + + const textesModificateursIds = new Set() + for (const infosTexteModificateur of Object.values( + context.infosTexteModificateurById, + )) { + if (infosTexteModificateur.CREATE !== undefined) { + textesModificateursIds.add(infosTexteModificateur.CREATE) + } + if (infosTexteModificateur.DELETE !== undefined) { + textesModificateursIds.add(infosTexteModificateur.DELETE) + } + } + + const textesModificateursIdByDate: Record = {} + for (const texteModificateurId of textesModificateursIds) { + let date: string + let texteVersionModificateur: + | JorfTexteVersion + | LegiTexteVersion + | TexteManquant = context.texteManquantById[ + texteModificateurId + ] as TexteManquant + if (texteVersionModificateur === undefined) { + texteVersionModificateur = (await getOrLoadTexteVersion( + context, + texteModificateurId, + )) as JorfTexteVersion | LegiTexteVersion + date = + texteVersionModificateur.META.META_SPEC.META_TEXTE_CHRONICLE.DATE_TEXTE + } else { + date = texteVersionModificateur.date + } + let textesModificateursId = textesModificateursIdByDate[date] + if (textesModificateursId === undefined) { + textesModificateursId = textesModificateursIdByDate[date] = [] + } + textesModificateursId.push(texteModificateurId) + } + + // Generation of Git repository + + await fs.remove(targetDir) + await fs.mkdir(targetDir, { recursive: true }) + await git.init({ + defaultBranch: "main", + dir: targetDir, + fs, + }) + + const codeTitle = + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL ?? + texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITRE ?? + texteVersion.META.META_COMMUN.ID + const codeDirName = slugify(codeTitle, "_") + const codeRepositoryRelativeDir = codeDirName + await fs.writeFile( + path.join(targetDir, "README.md"), + dedent` + # Codes juridiques français en Git et Markdown + + - [${codeTitle}](${codeDirName}) + `, + ) + await git.add({ + dir: targetDir, + filepath: "README.md", + fs, + }) + await git.commit({ + dir: targetDir, + fs, + author: { + email: "codes_juridiques@tricoteuses.fr", + name: "République française", + }, + message: "Création du README.md", + }) + + for (const [date, textesModificateursId] of Object.entries( + textesModificateursIdByDate, + ).toSorted(([date1], [date2]) => date1.localeCompare(date2))) { + console.log(date) + for (const texteModificateurId of textesModificateursId.toSorted()) { + let texteVersionModificateur: + | JorfTexteVersion + | LegiTexteVersion + | TexteManquant = context.texteManquantById[ + texteModificateurId + ] as TexteManquant + let texteModificateurTitle: string + if (texteVersionModificateur === undefined) { + texteVersionModificateur = (await getOrLoadTexteVersion( + context, + texteModificateurId, + )) as JorfTexteVersion | LegiTexteVersion + texteModificateurTitle = + texteVersionModificateur.META.META_SPEC.META_TEXTE_VERSION + .TITREFULL ?? + texteVersionModificateur.META.META_SPEC.META_TEXTE_VERSION.TITRE ?? + texteVersionModificateur.META.META_COMMUN.ID + } else { + texteModificateurTitle = `!!! Texte non trouvé ${date} !!!` + } + console.log(` ${texteModificateurId} ${texteModificateurTitle}`) + + await generateGitDirectory( + context, + 2, + codeTitle, + liensArticles, + textelrStructure?.LIEN_SECTION_TA, + codeRepositoryRelativeDir, + texteModificateurId, + ) + await git.commit({ + dir: targetDir, + fs, + author: { + email: "codes_juridiques@tricoteuses.fr", + name: "République française", + }, + message: texteModificateurTitle, + }) + } + } +} + +async function generateGitDirectory( + context: Context, + depth: number, + title: string, + liensArticles: LegiSectionTaLienArt[] | undefined, + liensSectionTa: LegiSectionTaLienSectionTa[] | undefined, + repositoryRelativeDir: string, + texteModificateurId: string, +) { + await fs.ensureDir(path.join(context.targetDir, repositoryRelativeDir)) + const readmeLinks: Array<{ href: string; title: string }> = [] + + if (liensArticles !== undefined) { + for (const lienArticle of liensArticles) { + const articleId = lienArticle["@id"] + const article = (await getOrLoadArticle( + context, + articleId, + )) as LegiArticle + const articleTitle = `Article ${article.META.META_SPEC.META_ARTICLE.NUM ?? articleId}` + const articleFilename = `${slugify(articleTitle, "_")}.md` + const articleRepositoryRelativeFilePath = path.join( + repositoryRelativeDir, + articleFilename, + ) + const infosTextModificateur = + context.infosTexteModificateurById[articleId] + if (context.currentInternalIds.has(articleId)) { + if (infosTextModificateur.DELETE === texteModificateurId) { + await fs.remove( + path.join(context.targetDir, articleRepositoryRelativeFilePath), + ) + context.currentInternalIds.delete(articleId) + continue + } + } else { + if (infosTextModificateur.CREATE === texteModificateurId) { + context.currentInternalIds.add(articleId) + } else { + continue + } + } + await fs.writeFile( + path.join(context.targetDir, articleRepositoryRelativeFilePath), + dedent` + ###### ${articleTitle} + + ${article.BLOC_TEXTUEL?.CONTENU} + `, + ) + await git.add({ + dir: context.targetDir, + filepath: articleRepositoryRelativeFilePath, + fs, + }) + readmeLinks.push({ href: articleFilename, title: articleTitle }) + } + } + + if (liensSectionTa !== undefined) { + for (const lienSectionTa of liensSectionTa) { + const sectionTaId = lienSectionTa["@id"] + const sectionTa = (await getOrLoadSectionTa( + context, + sectionTaId, + )) as LegiSectionTa + const sectionTaTitle = sectionTa.TITRE_TA ?? sectionTaId + const sectionTaDirName = slugify(sectionTaTitle.split(":")[0].trim(), "_") + const sectionTaRepositoryRelativeDir = path.join( + repositoryRelativeDir, + sectionTaDirName, + ) + const infosTextModificateur = + context.infosTexteModificateurById[sectionTaId] + if (context.currentInternalIds.has(sectionTaId)) { + if (infosTextModificateur.DELETE === texteModificateurId) { + await fs.remove( + path.join(context.targetDir, sectionTaRepositoryRelativeDir), + ) + context.currentInternalIds.delete(sectionTaId) + continue + } + } else { + if (infosTextModificateur.CREATE === texteModificateurId) { + context.currentInternalIds.add(sectionTaId) + } else { + continue + } + } + readmeLinks.push({ href: sectionTaDirName, title: sectionTaTitle }) + } + } + + const readmeRepositoryRelativeFilePath = path.join( + repositoryRelativeDir, + "README.md", + ) + await fs.writeFile( + path.join(context.targetDir, readmeRepositoryRelativeFilePath), + dedent` + ${"#".repeat(Math.min(depth, 6))} ${title} + + ${readmeLinks.map(({ href, title }) => `- [${title}](${href})`).join("\n")} + `, + ) + await git.add({ + dir: context.targetDir, + filepath: readmeRepositoryRelativeFilePath, + fs, + }) + + if (liensSectionTa !== undefined) { + for (const lienSectionTa of liensSectionTa) { + const sectionTaId = lienSectionTa["@id"] + if (context.currentInternalIds.has(sectionTaId)) { + const sectionTa = (await getOrLoadSectionTa( + context, + sectionTaId, + )) as LegiSectionTa + const sectionTaTitle = sectionTa.TITRE_TA ?? sectionTaId + const sectionTaDirName = slugify( + sectionTaTitle.split(":")[0].trim(), + "_", + ) + const sectionTaRepositoryRelativeDir = path.join( + repositoryRelativeDir, + sectionTaDirName, + ) + await generateGitDirectory( + context, + depth + 1, + sectionTaTitle, + sectionTa?.STRUCTURE_TA?.LIEN_ART, + sectionTa?.STRUCTURE_TA?.LIEN_SECTION_TA, + sectionTaRepositoryRelativeDir, + texteModificateurId, + ) + } + } + } +} + +async function getOrLoadArticle( + context: Context, + articleId: string, +): Promise { + let article = context.articleById[articleId] + if (article === undefined) { + article = ( + await db<{ data: JorfArticle | LegiArticle }[]>` + SELECT data FROM article WHERE id = ${articleId} + ` + )[0]?.data + assert.notStrictEqual(article, undefined) + context.articleById[articleId] = article + } + return article +} + +async function getOrLoadSectionTa( + context: Context, + sectionTaId: string, +): Promise { + let sectionTa = context.sectionTaById[sectionTaId] + if (sectionTa === undefined) { + sectionTa = ( + await db<{ data: LegiSectionTa }[]>` + SELECT data FROM section_ta WHERE id = ${sectionTaId} + ` + )[0]?.data + assert.notStrictEqual(sectionTa, undefined) + context.sectionTaById[sectionTaId] = sectionTa + } + return sectionTa +} + +async function getOrLoadTexteVersion( + context: Context, + texteId: string, +): Promise { + let texteVersion: JorfTexteVersion | LegiTexteVersion | null = + context.texteVersionById[texteId] + if (texteVersion === undefined) { + texteVersion = ( + await db<{ data: JorfTexteVersion | LegiTexteVersion }[]>` + SELECT data FROM texte_version WHERE id = ${texteId} + ` + )[0]?.data + if (texteVersion === undefined) { + console.warn(`Texte ${texteId} not found in table texte_version`) + texteVersion = null + } + context.texteVersionById[texteId] = texteVersion + } + return texteVersion +} + +async function registerLegiArticleModifiers( context: Context, legiTexteId: string, depth: number, @@ -191,8 +622,8 @@ async function exportLegiArticle( ) for (const articleLien of await db` - SELECT * FROM article_lien WHERE id = ${lienArticle["@id"]} - `) { + SELECT * FROM article_lien WHERE id = ${lienArticle["@id"]} + `) { assert.strictEqual(articleLien.cidtexte, legiTexteId) if (articleLien.article_id in context.legiTexteInternalIds) { // Ignore internal links because a LEGI texte can't modify itself. @@ -216,13 +647,27 @@ async function exportLegiArticle( ) } else if ( articleLien.typelien === "CITATION" || - (articleLien.typelien === "CODIFICATION" && articleLien.cible) || (articleLien.typelien === "SPEC_APPLI" && articleLien.cible) || (articleLien.typelien === "PILOTE_SUIVEUR" && !articleLien.cible) || (articleLien.typelien === "TXT_ASSOCIE" && !articleLien.cible) || articleLien.typelien === "TXT_SOURCE" ) { // Ignore link. + } else if ( + (articleLien.typelien === "CODIFICATION" && articleLien.cible) || + (articleLien.typelien === "CREATION" && articleLien.cible) || + (articleLien.typelien === "CREE" && !articleLien.cible) || + (articleLien.typelien === "MODIFICATION" && articleLien.cible) || + (articleLien.typelien === "MODIFIE" && !articleLien.cible) + ) { + await addArticleModificateurId( + context, + articleLien.article_id, + "CREATE", + articleId, + articleDateDebut, + articleDateFin, + ) } else if ( (articleLien.typelien === "CONCORDANCE" && articleLien.cible) || (articleLien.typelien === "CONCORDE" && !articleLien.cible) @@ -249,20 +694,6 @@ async function exportLegiArticle( articleDateDebut, articleDateFin, ) - } else if ( - (articleLien.typelien === "CREATION" && articleLien.cible) || - (articleLien.typelien === "CREE" && !articleLien.cible) || - (articleLien.typelien === "MODIFICATION" && articleLien.cible) || - (articleLien.typelien === "MODIFIE" && !articleLien.cible) - ) { - await addArticleModificateurId( - context, - articleLien.article_id, - "CREATE", - articleId, - articleDateDebut, - articleDateFin, - ) } else if (articleLien.typelien === "CREATION" && !articleLien.cible) { // It seems to be an error. // Ignore link. @@ -307,8 +738,8 @@ async function exportLegiArticle( } for (const texteVersionLien of await db` - SELECT * FROM texte_version_lien WHERE id = ${lienArticle["@id"]} - `) { + SELECT * FROM texte_version_lien WHERE id = ${lienArticle["@id"]} + `) { assert.strictEqual(texteVersionLien.cidtexte, legiTexteId) if (texteVersionLien.texte_version_id in context.legiTexteInternalIds) { // Ignore internal links because a LEGI texte can't modify itself. @@ -332,17 +763,18 @@ async function exportLegiArticle( ) } else if ( texteVersionLien.typelien === "CITATION" || - (texteVersionLien.typelien === "CODIFICATION" && - texteVersionLien.cible) || (texteVersionLien.typelien === "SPEC_APPLI" && texteVersionLien.cible) || (texteVersionLien.typelien === "TXT_SOURCE" && !texteVersionLien.cible) ) { // Ignore link. } else if ( - texteVersionLien.typelien === "CONCORDANCE" && - texteVersionLien.cible + (texteVersionLien.typelien === "CODIFICATION" && + texteVersionLien.cible) || + (texteVersionLien.typelien === "CREATION" && texteVersionLien.cible) || + (texteVersionLien.typelien === "MODIFICATION" && + texteVersionLien.cible) || + (texteVersionLien.typelien === "RECTIFICATION" && texteVersionLien.cible) ) { - // L'article est créé par le déplacement d'un article existant dans une loi. await addTexteModificateurId( context, texteVersionLien.texte_version_id, @@ -352,11 +784,10 @@ async function exportLegiArticle( articleDateFin, ) } else if ( - (texteVersionLien.typelien === "CREATION" && texteVersionLien.cible) || - (texteVersionLien.typelien === "MODIFICATION" && - texteVersionLien.cible) || - (texteVersionLien.typelien === "RECTIFICATION" && texteVersionLien.cible) + texteVersionLien.typelien === "CONCORDANCE" && + texteVersionLien.cible ) { + // L'article est créé par le déplacement d'un article existant dans une loi. await addTexteModificateurId( context, texteVersionLien.texte_version_id, @@ -418,8 +849,6 @@ async function exportLegiArticle( ) } else if ( articleLien["@typelien"] === "CITATION" || - (articleLien["@typelien"] === "CODIFICATION" && - articleLien["@sens"] === "source") || (articleLien["@typelien"] === "HISTO" && articleLien["@sens"] === "source") /* Au moins sur un exemple, le lien est vide à part ces 2 champs */ || @@ -432,6 +861,26 @@ async function exportLegiArticle( articleLien["@typelien"] === "TXT_SOURCE" ) { // Ignore link. + } else if ( + (articleLien["@typelien"] === "CODIFICATION" && + articleLien["@sens"] === "source") || + (articleLien["@typelien"] === "CREATION" && + articleLien["@sens"] === "source") || + (articleLien["@typelien"] === "CREE" && + articleLien["@sens"] === "cible") || + (articleLien["@typelien"] === "MODIFICATION" && + articleLien["@sens"] === "source") || + (articleLien["@typelien"] === "MODIFIE" && + articleLien["@sens"] === "cible") + ) { + await addTexteModificateurId( + context, + articleLien["@cidtexte"], + "CREATE", + articleId, + articleDateDebut, + articleDateFin, + ) } else if ( (articleLien["@typelien"] === "CONCORDANCE" && articleLien["@sens"] === "source") || @@ -462,24 +911,6 @@ async function exportLegiArticle( articleDateDebut, articleDateFin, ) - } else if ( - (articleLien["@typelien"] === "CREATION" && - articleLien["@sens"] === "source") || - (articleLien["@typelien"] === "CREE" && - articleLien["@sens"] === "cible") || - (articleLien["@typelien"] === "MODIFICATION" && - articleLien["@sens"] === "source") || - (articleLien["@typelien"] === "MODIFIE" && - articleLien["@sens"] === "cible") - ) { - await addTexteModificateurId( - context, - articleLien["@cidtexte"], - "CREATE", - articleId, - articleDateDebut, - articleDateFin, - ) } else if ( articleLien["@typelien"] === "CREATION" && articleLien["@sens"] === "cible" @@ -563,10 +994,11 @@ async function exportLegiArticle( date: articleDateDebut, } } + infosTexteModificateur.CREATE = texteManquantId } } -async function exportLegiSectionTa( +async function registerLegiSectionTaModifiers( context: Context, legiTexteId: string, depth: number, @@ -697,433 +1129,10 @@ async function exportLegiSectionTa( date: sectionTaDateDebut, } } + infosTexteModificateur.CREATE = texteManquantId } } -async function exportLegiTexteToMarkdown( - legiTexteId: string, - targetDir: string, -): Promise { - const context: Context = { - articleById: {}, - currentInternalIds: new Set(), - infosArticleModificateurById: {}, - infosTexteModificateurById: {}, - legiTexteInternalIds: new Set(), - sectionTaById: {}, - targetDir, - texteManquantById: {}, - texteVersionById: {}, - } - context.legiTexteInternalIds.add(legiTexteId) - - const texteVersion = (await getOrLoadTexteVersion(context, legiTexteId)) as - | JorfTexteVersion - | LegiTexteVersion - assert.notStrictEqual(texteVersion, null) - - const textelr = ( - await db<{ data: LegiTextelr }[]>` - SELECT data FROM textelr WHERE id = ${legiTexteId} - ` - )[0]?.data - assert.notStrictEqual(textelr, undefined) - - const meta = texteVersion.META - const metaTexteVersion = meta.META_SPEC.META_TEXTE_VERSION - console.log( - `${meta.META_COMMUN.ID} ${metaTexteVersion.TITREFULL ?? metaTexteVersion.TITRE ?? meta.META_COMMUN.ID} (${metaTexteVersion.DATE_DEBUT ?? ""} — ${metaTexteVersion.DATE_FIN === "2999-01-01" ? "…" : (metaTexteVersion.DATE_FIN ?? "")}, ${metaTexteVersion.ETAT})`, - ) - - // First Pass: Register IDs of internal objects - - const textelrStructure = textelr.STRUCT - const liensArticles = textelrStructure?.LIEN_ART - if (liensArticles !== undefined) { - for (const lienArticle of liensArticles) { - context.legiTexteInternalIds.add(lienArticle["@id"]) - } - } - - for await (const { sectionTa } of walkStructureTree( - context, - textelrStructure as LegiSectionTaStructure, - )) { - const liensArticles = sectionTa?.STRUCTURE_TA?.LIEN_ART - if (liensArticles !== undefined) { - for (const lienArticle of liensArticles) { - context.legiTexteInternalIds.add(lienArticle["@id"]) - } - } - } - - // Second Pass - - if (liensArticles !== undefined) { - for (const lienArticle of liensArticles) { - const article = (await getOrLoadArticle( - context, - lienArticle["@id"], - )) as LegiArticle - await exportLegiArticle(context, legiTexteId, 0, lienArticle, article) - } - } - - for await (const { - lienSectionTa, - parentsSectionTa, - sectionTa, - } of walkStructureTree(context, textelrStructure as LegiSectionTaStructure)) { - await exportLegiSectionTa( - context, - legiTexteId, - parentsSectionTa.length + 1, - lienSectionTa, - sectionTa, - ) - - const liensArticles = sectionTa?.STRUCTURE_TA?.LIEN_ART - if (liensArticles !== undefined) { - for (const lienArticle of liensArticles) { - const article = (await getOrLoadArticle( - context, - lienArticle["@id"], - )) as LegiArticle - await exportLegiArticle( - context, - legiTexteId, - parentsSectionTa.length + 2, - lienArticle, - article, - ) - } - } - } - - // Sort of textes modificateurs by date - - const textesModificateursIds = new Set() - for (const infosTexteModificateur of Object.values( - context.infosTexteModificateurById, - )) { - if (infosTexteModificateur.CREATE !== undefined) { - textesModificateursIds.add(infosTexteModificateur.CREATE) - } - if (infosTexteModificateur.DELETE !== undefined) { - textesModificateursIds.add(infosTexteModificateur.DELETE) - } - } - - const textesModificateursIdByDate: Record = {} - for (const texteModificateurId of textesModificateursIds) { - let date: string - let texteVersionModificateur: - | JorfTexteVersion - | LegiTexteVersion - | TexteManquant = context.texteManquantById[ - texteModificateurId - ] as TexteManquant - if (texteVersionModificateur === undefined) { - texteVersionModificateur = (await getOrLoadTexteVersion( - context, - texteModificateurId, - )) as JorfTexteVersion | LegiTexteVersion - date = - texteVersionModificateur.META.META_SPEC.META_TEXTE_CHRONICLE.DATE_TEXTE - } else { - date = texteVersionModificateur.date - } - let textesModificateursId = textesModificateursIdByDate[date] - if (textesModificateursId === undefined) { - textesModificateursId = textesModificateursIdByDate[date] = [] - } - textesModificateursId.push(texteModificateurId) - } - - // Generation of Git repository - - await fs.remove(targetDir) - await fs.mkdir(targetDir, { recursive: true }) - await git.init({ - defaultBranch: "main", - dir: targetDir, - fs, - }) - - const codeTitle = - texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITREFULL ?? - texteVersion.META.META_SPEC.META_TEXTE_VERSION.TITRE ?? - texteVersion.META.META_COMMUN.ID - const codeDirName = slugify(codeTitle, "_") - const codeRepositoryRelativeDir = codeDirName - await fs.writeFile( - path.join(targetDir, "README.md"), - dedent` - # Codes juridiques en Git et Markdown - - - [${codeTitle}](${codeDirName}) - `, - ) - await git.add({ - dir: targetDir, - filepath: "README.md", - fs, - }) - await git.commit({ - dir: targetDir, - fs, - author: { - email: "codes_juridiques@tricoteuses.fr", - name: "République française", - }, - message: "Création du README.md", - }) - - for (const [date, textesModificateursId] of Object.entries( - textesModificateursIdByDate, - ).toSorted(([date1], [date2]) => date1.localeCompare(date2))) { - console.log(date) - for (const texteModificateurId of textesModificateursId.toSorted()) { - let texteVersionModificateur: - | JorfTexteVersion - | LegiTexteVersion - | TexteManquant = context.texteManquantById[ - texteModificateurId - ] as TexteManquant - let texteModificateurTitle: string - if (texteVersionModificateur === undefined) { - texteVersionModificateur = (await getOrLoadTexteVersion( - context, - texteModificateurId, - )) as JorfTexteVersion | LegiTexteVersion - texteModificateurTitle = - texteVersionModificateur.META.META_SPEC.META_TEXTE_VERSION - .TITREFULL ?? - texteVersionModificateur.META.META_SPEC.META_TEXTE_VERSION.TITRE ?? - texteVersionModificateur.META.META_COMMUN.ID - } else { - texteModificateurTitle = "!!! Texte non trouvé !!!" - } - console.log(` ${texteModificateurId} ${texteModificateurTitle}`) - - await generateGitDirectory( - context, - 2, - codeTitle, - liensArticles, - textelrStructure?.LIEN_SECTION_TA, - codeRepositoryRelativeDir, - texteModificateurId, - ) - await git.commit({ - dir: targetDir, - fs, - author: { - email: "codes_juridiques@tricoteuses.fr", - name: "République française", - }, - message: texteModificateurTitle, - }) - } - } -} - -async function generateGitDirectory( - context: Context, - depth: number, - title: string, - liensArticles: LegiSectionTaLienArt[] | undefined, - liensSectionTa: LegiSectionTaLienSectionTa[] | undefined, - repositoryRelativeDir: string, - texteModificateurId: string, -) { - await fs.ensureDir(path.join(context.targetDir, repositoryRelativeDir)) - const readmeLinks: Array<{ href: string; title: string }> = [] - - if (liensArticles !== undefined) { - for (const lienArticle of liensArticles) { - const articleId = lienArticle["@id"] - const article = (await getOrLoadArticle( - context, - articleId, - )) as LegiArticle - const articleTitle = `Article ${article.META.META_SPEC.META_ARTICLE.NUM ?? articleId}` - const articleFilename = `${slugify(articleTitle, "_")}.md` - const articleRepositoryRelativeFilePath = path.join( - repositoryRelativeDir, - articleFilename, - ) - const infosTextModificateur = - context.infosTexteModificateurById[articleId] - if (context.currentInternalIds.has(articleId)) { - if (infosTextModificateur.DELETE === texteModificateurId) { - await fs.remove( - path.join(context.targetDir, articleRepositoryRelativeFilePath), - ) - context.currentInternalIds.delete(articleId) - continue - } - } else { - if (infosTextModificateur.CREATE === texteModificateurId) { - context.currentInternalIds.add(articleId) - } else { - continue - } - } - await fs.writeFile( - path.join(context.targetDir, articleRepositoryRelativeFilePath), - dedent` - ###### ${articleTitle} - - ${article.BLOC_TEXTUEL?.CONTENU} - `, - ) - await git.add({ - dir: context.targetDir, - filepath: articleRepositoryRelativeFilePath, - fs, - }) - readmeLinks.push({ href: articleFilename, title: articleTitle }) - } - } - - if (liensSectionTa !== undefined) { - for (const lienSectionTa of liensSectionTa) { - const sectionTaId = lienSectionTa["@id"] - const sectionTa = (await getOrLoadSectionTa( - context, - sectionTaId, - )) as LegiSectionTa - const sectionTaTitle = - sectionTa.TITRE_TA?.split(":")[0].trim() ?? sectionTaId - const sectionTaDirName = slugify(sectionTaTitle, "_") - const sectionTaRepositoryRelativeDir = path.join( - repositoryRelativeDir, - sectionTaDirName, - ) - const infosTextModificateur = - context.infosTexteModificateurById[sectionTaId] - if (context.currentInternalIds.has(sectionTaId)) { - if (infosTextModificateur.DELETE === texteModificateurId) { - await fs.remove( - path.join(context.targetDir, sectionTaRepositoryRelativeDir), - ) - context.currentInternalIds.delete(sectionTaId) - continue - } - } else { - if (infosTextModificateur.CREATE === texteModificateurId) { - context.currentInternalIds.add(sectionTaId) - } else { - continue - } - } - readmeLinks.push({ href: sectionTaDirName, title: sectionTaTitle }) - } - } - - const readmeRepositoryRelativeFilePath = path.join( - repositoryRelativeDir, - "README.md", - ) - await fs.writeFile( - path.join(context.targetDir, readmeRepositoryRelativeFilePath), - dedent` - ${"#".repeat(Math.min(depth, 6))} ${title} - - ${readmeLinks.map(({ href, title }) => `- [${title}](${href})`).join("\n")} - `, - ) - await git.add({ - dir: context.targetDir, - filepath: readmeRepositoryRelativeFilePath, - fs, - }) - - if (liensSectionTa !== undefined) { - for (const lienSectionTa of liensSectionTa) { - const sectionTaId = lienSectionTa["@id"] - if (context.currentInternalIds.has(sectionTaId)) { - const sectionTa = (await getOrLoadSectionTa( - context, - sectionTaId, - )) as LegiSectionTa - const sectionTaTitle = - sectionTa.TITRE_TA?.split(":")[0].trim() ?? sectionTaId - const sectionTaDirName = slugify(sectionTaTitle, "_") - const sectionTaRepositoryRelativeDir = path.join( - repositoryRelativeDir, - sectionTaDirName, - ) - await generateGitDirectory( - context, - depth + 1, - sectionTaTitle, - sectionTa?.STRUCTURE_TA?.LIEN_ART, - sectionTa?.STRUCTURE_TA?.LIEN_SECTION_TA, - sectionTaRepositoryRelativeDir, - texteModificateurId, - ) - } - } - } -} - -async function getOrLoadArticle( - context: Context, - articleId: string, -): Promise { - let article = context.articleById[articleId] - if (article === undefined) { - article = ( - await db<{ data: JorfArticle | LegiArticle }[]>` - SELECT data FROM article WHERE id = ${articleId} - ` - )[0]?.data - assert.notStrictEqual(article, undefined) - context.articleById[articleId] = article - } - return article -} - -async function getOrLoadSectionTa( - context: Context, - sectionTaId: string, -): Promise { - let sectionTa = context.sectionTaById[sectionTaId] - if (sectionTa === undefined) { - sectionTa = ( - await db<{ data: LegiSectionTa }[]>` - SELECT data FROM section_ta WHERE id = ${sectionTaId} - ` - )[0]?.data - assert.notStrictEqual(sectionTa, undefined) - context.sectionTaById[sectionTaId] = sectionTa - } - return sectionTa -} - -async function getOrLoadTexteVersion( - context: Context, - texteId: string, -): Promise { - let texteVersion: JorfTexteVersion | LegiTexteVersion | null = - context.texteVersionById[texteId] - if (texteVersion === undefined) { - texteVersion = ( - await db<{ data: JorfTexteVersion | LegiTexteVersion }[]>` - SELECT data FROM texte_version WHERE id = ${texteId} - ` - )[0]?.data - if (texteVersion === undefined) { - console.warn(`Texte ${texteId} not found in table texte_version`) - texteVersion = null - } - context.texteVersionById[texteId] = texteVersion - } - return texteVersion -} - async function* walkStructureTree( context: Context, structure: LegiSectionTaStructure,