Skip to content

Commit

Permalink
feat: add a 'publicodes dev' command
Browse files Browse the repository at this point in the history
  • Loading branch information
johangirod committed Jan 27, 2025
1 parent 2ad8c4f commit a9b6ced
Show file tree
Hide file tree
Showing 20 changed files with 1,478 additions and 52 deletions.
20 changes: 16 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
},
"files": [
"dist",
"bin"
"bin",
"quick-doc"
],
"exports": {
".": {
Expand Down Expand Up @@ -62,18 +63,28 @@
"dependencies": {
"@clack/prompts": "^0.7.0",
"@oclif/core": "^4.0.23",
"@publicodes/react-ui": "^1.5.4",
"@tailwindcss/typography": "^0.5.16",
"@tailwindcss/vite": "^4.0.0",
"@types/node": "^18.11.18",
"chalk": "^5.3.0",
"chokidar": "^4.0.3",
"glob": "^10.4.1",
"path": "^0.12.7",
"publicodes": "^1.6.1",
"yaml": "^2.4.5"
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.3",
"tailwindcss": "^4.0.0",
"vite": "^6.0.11",
"yaml": "^2.7.0"
},
"devDependencies": {
"@oclif/test": "^4.0.9",
"@types/jest": "^29.5.13",
"@types/react": "^19.0.8",
"docdash": "^2.0.1",
"prettier": "^3.0.0",
"prettier": "^3.4.2",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typedoc": "^0.24.8",
Expand Down Expand Up @@ -106,5 +117,6 @@
},
"publishConfig": {
"access": "public"
}
},
"packageManager": "[email protected]+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}
Empty file added quick-doc/README.md
Empty file.
15 changes: 15 additions & 0 deletions quick-doc/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>⚡️ QuickDoc - Publicodes</title>
<!-- <link rel="stylesheet" href="./src/index.css" /> -->
</head>
<body>
<div id="root"></div>

<script type="module" src="/src/index.tsx"></script>
<link href="/src/app.css" rel="stylesheet" />
</body>
</html>
63 changes: 63 additions & 0 deletions quick-doc/src/app.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
@import 'tailwindcss';

@layer base {
h1 {
@apply text-4xl font-bold mt-8 mb-6 text-[#10162F];
}

h2 {
@apply text-3xl font-semibold mt-6 mb-5 text-[#10162F];
}

h3 {
@apply text-2xl font-semibold mt-5 mb-4 text-[#10162F];
}

p {
@apply text-base leading-relaxed mb-4 text-[#4B5563];
}

ul {
@apply list-disc list-inside mb-4 space-y-2;
}

ol {
@apply list-decimal list-inside mb-4 space-y-2;
}

li {
@apply text-[#4B5563] leading-relaxed;
}

a {
@apply text-[#2975d1] hover:text-[#1a365d] transition-colors underline-offset-2 hover:underline;
}

blockquote {
@apply pl-4 border-l-4 border-[#E5E7EB] italic my-4;
}

code {
@apply bg-[#F3F4F6] px-2 py-1 rounded text-sm text-[#10162F];
}

pre {
@apply bg-[#F3F4F6] p-4 rounded-lg mb-4 overflow-x-auto;
}
button {
@apply bg-slate-100 px-2 py-1 rounded-md hover:bg-slate-50 transition-colors cursor-pointer disabled:opacity-50 text-sm disabled:cursor-not-allowed border border-slate-600 text-slate-800 ml-2;
}
}

@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.animate-fade-in {
animation: fade-in 0.15s ease-out;
}
73 changes: 73 additions & 0 deletions quick-doc/src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { RulePage } from '@publicodes/react-ui'
import { useEffect, useState } from 'react'
import {
BrowserRouter,
Link,
Navigate,
Route,
Routes,
useParams,
} from 'react-router-dom'
import { engine, onEngineUpdate } from '../engine'
import { sitemap } from '../sitemap'
import { onSituationUpdate, situations } from '../situations'
import Header from './Header'
import { Error } from './Error'

function RulePageWrapper() {
let { '*': splat } = useParams()
return (
<RulePage
engine={engine}
documentationPath=""
rulePath={splat}
searchBar={true}
showDevSection={true}
language="fr"
renderers={{ Link: Link }}
/>
)
}

export default function App() {
const [, forceUpdate] = useState({})
useEffect(() => {
// Subscribe to engine updates
return onEngineUpdate(() => forceUpdate({}))
}, [])
const [activeSituation, setActiveSituation] = useState('')

useEffect(() => {
return onSituationUpdate(() => {
engine.setSituation(situations[activeSituation] ?? {})
setActiveSituation(activeSituation in situations ? activeSituation : '')
forceUpdate({})
})
}, [activeSituation])

function handleSituationChange(situation: string) {
setActiveSituation(situation)
engine.setSituation(situations[situation] ?? {})
}

return (
<>
<BrowserRouter>
<Header
setSituation={handleSituationChange}
activeSituation={activeSituation}
/>
<div className="container mx-auto px-4">
<Error />
<Routes>
<Route
path="/"
element={<Navigate to={Object.keys(sitemap)[0]} replace />}
/>
<Route path="/*" element={<RulePageWrapper />} />
</Routes>
</div>
</BrowserRouter>
</>
)
}
76 changes: 76 additions & 0 deletions quick-doc/src/components/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React from 'react'
import { error } from '../engine'

export function Error() {
if (error.length === 0) return null
return (
<>
<div
className="fixed z-[1000]
inset-0 bg-slate-400/50 backdrop-blur-[2px] animate-fade-in "
/>
<div
className="fixed z-[1000] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-full max-w-xl bg-white rounded-lg shadow-lg animate-slide-up"
role="alert"
>
<div className="p-6">
<div className="space-y-2 max-h-[70vh] overflow-auto">
{error.map((err, index) => {
const { type, message, ruleName } = parseError(err)
return (
<React.Fragment key={index}>
<div className="flex items-center gap-3 ">
<svg
className="w-6 h-6 text-red-500 flex-shrink-0"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
<h2 className="text-lg m-0 py-4 font-semibold text-red-500">
{type}
</h2>
</div>
<p className="text-xl font-semibold flex items-baseline">
Dans la règle
<code className="px-1 ml-2 text-slate-800 bg-gray-100 rounded-md">
{ruleName}
</code>
</p>

<p key={index} className="text-gray-600 ">
{message}
</p>
</React.Fragment>
)
})}
</div>
</div>
</div>
</>
)
}

/**
*
* @example
* ```js
* parseError(`[ Erreur syntaxique ] ➡️ Dans la règle "salaire net" ✖️ La référence "salaire brut" est introuvable. Vérifiez que l'orthographe et l'espace de nom sont corrects`)
* ```
*/
function parseError(error: string): {
type: string
message: string
ruleName: string
} {
const type = error.match(/\[ (.+?) \]/)?.[1] ?? ''
const ruleName = error.match(/"(.+?)"/)?.[1] ?? ''
const message = error.split('✖️')[1].trim()
return { type, message, ruleName }
}
46 changes: 46 additions & 0 deletions quick-doc/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Link } from 'react-router-dom'
import { situations } from '../situations'

export default function Header({
setSituation,
activeSituation,
}: {
setSituation: (situation: string) => void
activeSituation: string
}) {
return (
<header className=" container mx-auto p-4">
<div className="flex items-center justify-between flex-col md:flex-row">
<h1>
<Link to="/" className="text-xl font-bold">
⚡ Quick-doc
</Link>
</h1>

<nav className="w-full md:w-auto ">
<div className="flex flex-col gap-2">
<label
htmlFor="situation-select"
className="text-sm font-medium text-gray-700"
>
Selectionner une situation
</label>
<select
id="situation-select"
value={activeSituation}
onChange={(e) => setSituation(e.target.value)}
className="block w-full md:w-64 px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
>
<option value=""></option>
{Object.keys(situations).map((situationName) => (
<option key={situationName} value={situationName}>
{situationName}
</option>
))}
</select>
</div>
</nav>
</div>
</header>
)
}
Empty file.
17 changes: 17 additions & 0 deletions quick-doc/src/components/RulesIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Link } from 'react-router-dom'
import { sitemap } from '../sitemap'

export function RulesIndex() {
return (
<div>
<h1>Index des règles</h1>
<ul>
{Object.entries(sitemap).map(([path, name]) => (
<li key={path}>
<Link to={path}>{name}</Link>
</li>
))}
</ul>
</div>
)
}
49 changes: 49 additions & 0 deletions quick-doc/src/engine.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import Engine from 'publicodes'

// Create a custom event for engine updates
const ENGINE_UPDATED = 'engine-updated'
const engineUpdate = new EventTarget()

export let error: string[] = []
export let log: string[] = []
export let warning: string[] = []

const logger = {
log: (message: string) => log.push(message),
warn: (message: string) => warning.push(message),
error: (message: string) => error.push(message),
}

export let engine = new Engine()
try {
engine = new Engine(__INJECTED_RULES__, { logger })
} catch (e) {
error = [e.message]
engineUpdate.dispatchEvent(new Event(ENGINE_UPDATED))
}

// Helper to subscribe to engine updates
export function onEngineUpdate(callback: () => void) {
engineUpdate.addEventListener(ENGINE_UPDATED, callback)
return () => engineUpdate.removeEventListener(ENGINE_UPDATED, callback)
}
function clearLogs() {
error = []
log = []
warning = []
}

if (import.meta.hot) {
import.meta.hot.on('rules-updated', (newRules) => {
const previousEngine = engine
clearLogs()
try {
engine = new Engine(newRules, { logger })
engine.setSituation(previousEngine.getSituation())
} catch (e) {
error = [e.message]
}
// Dispatch event to notify subscribers
engineUpdate.dispatchEvent(new Event(ENGINE_UPDATED))
})
}
Loading

0 comments on commit a9b6ced

Please sign in to comment.