Documentation des classes CSS
#1
Salut salut,

j'ai, dans mes projets, des classes CSS/data-* attributes qui me servent à définir des comportements ainsi que des styles. Je cherche un système de documentation léger pour pouvoir les... documenter justement! : )

L'idée , c'est que dans mes codes, j'ai par exemple:

Code :
data-on-success="reload|goto:/url/to/target|"

Quand cet attribut est présent sur un "form", celui-ci sera soumis en via AJAX, et si la réponse est 200 success, alors soit la page sera rechargée ("reload") soit je redirige le joueur vers l'URL spécifiée ("goto:*") soit je ne fais rien (valeur vide).

Autre exemple, j'ai la classe "message", et les classes "info", "tips", "warning", "error" qui servent de styling aux info-bulles à droite dans ECLERD.

J'aurai aimé savoir si vous connaissez des systèmes de documentation légers pour pouvoir noter ça quelque part, histoire que, dans 6 mois, je sache encore à quoi servent ces classes/attributs. C'est surtout ceux qui ont des comportements customs définis dans le JS qui m'intéressent (autres exemples: a[@data-method="POST"] permet de soumettre un lien en POST plutôt qu'en GET, ou *[@data-increment="..."] incrémente la valeur du noeud de X toutes les secondes, etc)
Répondre
#2
Hmm j'ai pas tout compris mais en gros tu as fait une librairie dont on appelle les comportement en mettant des attributs dans le DOM ... Y a pas vraiment de solution pour auto-documenter ... tu dois faire la doc de ta librairie 2
Répondre
#3
Oui, c'est le principe de Rails unobstrusive Javascript. Les attributes data-remote, data-method, etc. Je crois que tu n'as pas le choix sinon de créer ta propre documentation.
Répondre
#4
Je suis d'accord qu'il faudra que je crée ma doc, c'est pas l'IDE qui va deviner que "data-on-success='reload'" signifie de recharger la page et que cet attribut marche que sur un "form". Et merci Sephi, maintenant, j'ai une dénomination pour parler de ça (mieux que "le Javascript normal" ... :/ )

Ce que je cherchais (et cherche toujours) c'est la structure pour faire cette documentation facilement (y'a pas 50.000 classes/attributs, je n'ai pas envie de tracter 3 nouveaux serveurs et 200 dépendances), et avoir le genre de page du W3C/WHATWG ou autre forme de doc un peu "visuelle". Je ne sais pas trop ce qui existe pour ça. Dis autrement, comment "Rails Unobtrusive JavaScript" est-il documenté?

Pour l'instant, j'ai 2 pistes bateaux-simples: soit un fichier *.md dans le projet (et je jetterai un oeil voir comment faire des encarts "warning", "note", etc) soit un fichier HTML+CSS simple, et je mettrai ma doc dedans (ce qui me permettrait de l'avoir en ligne en même temps que le projet, c'est peut-être le plus sympa)
Répondre
#5
Je crois bien que c'est ici : https://github.com/rails/jquery-ujs/wiki
Répondre
#6
En effet, markdown m'a l'air tout simple et la structure de page (l'attribut en titre de la section et sa description en contenu) m'ont plu.

Mais après coup, je me suis aperçu que markdown, c'est hyper-chiant à générer à cause des indentations: faire la doc à la mano, ça veut dire que dans 2 semaines, elle ne correspondra plus au code source. Du coup, j'ai préféré générer cette doc à partir des sources JS, pour être sûr qu'elle soit à jour (à considéré que je relance la génération quand je déploie!)

DU coup, j'ai gardé le concept de titre = attribut/classe et contenu = description de son effet, mais je l'ai généré en HTML (ça permet aussi de faire du kikoo pour la présentation : ) )
J'en ai profité pour intégrer l'open API du jeu (enfin, un truc qui s'en approche): en gros, la liste des URLs avec le verbe HTTP correspondant. Il faudra que j'ajoute la liste des paramètres, leur type/domaine et la description de la réponse (mais bon, faudrait aussi que j'ajoute la doc de chacun de ces endpoints... on verra bien).

Voilà donc le résultat: https://eclerd.com/resources/doc/doc.html

Ca fait le taff que je voulais (liste des classes/attributs avec l'effet pour peu que je le documente correctement). Pour le reste (styling & open API), c'est juste que je me suis laissé entraîné 2
Répondre
#7
Sympa, tu parses ton code ou tu as utilisé un outil style javadoc ?
Répondre
#8
Je parse, vue que c'est un cas basique et que je n'ai pas pu faire marcher la Javadoc là dessus: ce ne sont pas des définitions de fonctions/classes. Le pattern est

Code :
(() => {
...
\t/**
\t * Documentation (multiligne si besoin, avec HTML si besoin)
\t */
\tdocument.querySelectorAll('<selector>')

Et habituellement, j'appelle "forEach" sur cette NodeList (parfois sur la même ligne, parfois à la ligne suivante... d'où le fait que je tronque le pattern à ce niveau là). Cela reste à peu près la même syntaxe, mais comme le querySelectorAll n'est pas une définition de fonction, je ne voyais pas comment intégrer ça à de la javadoc existante. Et je restreint uniquement aux fichiers JS des templates (si, pour un endpoint spécifique, j'ai ce pattern, alors je m'en fous un peu: c'est spécifique à une seule page, donc quand je travaille sur les autres, soit 90% du temps vu que j'ai plus d'une dizaine de pages, cela ne m'intéresse pas).
Du coup, le parsing tiens en 10 lignes grosso modo:



/**
* @return HtmlDocInfos[]
*/
private function buildHtmlDoc(): array {
$htmlDocs = array();
foreach ($this->jsFiles as $jsFile) {
$jsPath = realpath($jsFile);
$jsContent = file_get_contents($jsPath);
if (!preg_match_all(
'~(?:/\\*\\*' .
'((?:\\n\\s+\\*\\s+(?:.*))+)' .
'\\n\\s*\\*/)?' .
'\\n\\tdocument\\.querySelectorAll\\(\'(.+)\'\\)~m',
$jsContent,
$docs,
PREG_SET_ORDER)) {
continue;
}
foreach ($docs as $doc) {
$htmlDoc = new HtmlDocInfos();
$htmlDoc->file = $this->htmlDocUrlForPath($jsFile);
$htmlDoc->documentation = trim(
preg_replace_callback(
'~\\n\\s+\\* (.)~',
static function (array $match): string {
return (strtoupper($match[1]) === $match[1] ? "\n" : " ") . $match[1];
},
$doc[1] ?? ''));
$htmlDoc->selector = $doc[2];
$htmlDocs[] = $htmlDoc;
}
}

return $htmlDocs;
}

Où HtmlDocInfos est un simple bean; cette liste de beans est formattée en HTML en dessous.

Pour la construction de la doc "REST API", c'est un peu plus lourd: je vais parser le contenu des classes des endpoints, et j'en tire les informations correspondantes. J'avais vu, quand j'avais attaqué ce morceau-là pour le fun, qu'il existait des parser qui se basent sur la PHPDoc de la classe. Mais j'avais peur que cette PHPDoc ne soit alors jamais en phase avec le contenu du code du endpoint, d'où le parser de code.
Mais dans ce cas-là, ça montre clairement ses limites: je n'arrive pas à descendre aisément jusqu'au caractère "required/optional" des paramètres, ou à déterminer les réponses (codes HTTP et format) retournables par le endpoint... Je changerai peut-être de stratégie, et je passerai finalement par la PHPDoc, en rajoutant quelques inspections dans mon plugin IntelliJ pour s'assurer que la doc et le code soient en phase.
Actuellement, ça tiens en ça:



/**
* @return object
* @throws ReflectionException
*/
private function getOpenApiPaths(): object {
$paths = array();

$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$this->endpointsDirectory,
RecursiveDirectoryIterator::SKIP_DOTS
| RecursiveDirectoryIterator::CURRENT_AS_PATHNAME
| RecursiveDirectoryIterator::UNIX_PATHS),
RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($iterator as $it) {
if (strpos($it, 'Endpoint.php') === false) {
// Ignore non endpoint files
continue;
}

$endpointRelativePath = substr($it, strlen($this->endpointsDirectory), -4);
$endpointClass = $this->endpointBaseClass . str_replace('/', '\\', $endpointRelativePath);
$reflection = new ReflectionClass($endpointClass);

if ($reflection->isAbstract() || $reflection->isInterface()) {
// Don't treat interfaces or abstract classes, since they are utility classes
continue;
}

$endpointConstructor = $reflection->getConstructor();
if ($endpointConstructor !== null && count($endpointConstructor->getParameters()) > 0) {
// Endpoint requires a parameter: it cannot be a web endpoint (it's more like a CLI one) so ignore
continue;
}

$endpoint = new $endpointClass();

$skipEndpoint = $this->doSkipEndpoint($endpoint);

if ($skipEndpoint === true) {
continue;
} else if ($skipEndpoint === null) {
// Unexpected class!
throw new LogicException("The endpoint $endpointClass does not extend a known abstract class");
}

$path = array();
$classDoc = $this->cleanDocCommentLines($reflection->getDocComment());
$path['summary'] = $classDoc
? $classDoc[0]
: 'Not documented';
$path['description'] = $classDoc && count($classDoc) > 1
? implode("\n", array_slice($classDoc, 1))
: 'Not documented';

$parameters = array();
foreach ($reflection->getReflectionConstants() as $const) {
if (strpos($const->getName(), 'PARAM_') === 0) {
$parameters[] = (object)array(
'name' => $const->getValue(),
'in' => 'query',
'description' => implode("\n", $this->cleanDocCommentLines($const->getDocComment())) ?: 'Not documented'
/*'required' => true*/
);
}
}

$responses = array();
// $operation['responses'][200] = (object)array(
// 'description' => 'Not documented',
// 'content' => array(
// 'text/html' => (object)array(),
// 'application/json' => (object)array()));

$operation = array(
'operationId' => basename($it, '.php'),
'parameters' => $parameters,
'responses' => (object)$responses);

$verb = $this->getEndpointVerb($endpoint);
if ($verb === null) {
throw new LogicException("The web endpoint $endpointClass has unknown HTTP verb");
}
$path[$verb] = (object)$operation;

$paths['/' . dirname($endpointRelativePath) . '/'] = (object)$path;
}
return (object)$paths;
}
Répondre
#9
Hum, tu pourrais extraire tes fonctions de querySelector all, comme ça tu peux leur mettre un docblock au dessus et ne pas avoir à parser 'document.querySelectorAll'

Pour les routes openApi je partirais dans l'autre sens : créér la doc, et définir les routes en fonction de la doc, comme ça la doc api est forcément à jour.
Répondre
#10
Je ne vois pas comment les extraires sans devoir perdre le sélecteur (parce que je n'ai pas envie de le répliquer dans la doc) ou être obligé de faire 1 fichier/comportement (pour 8 comportements à tout casser, ça me fait un peu chier d'aller tracter ensuite des mergeurs de JS et tout le bouzin).

Générer les codes à partir de la doc, Springboot kiff faire ça (bon, ok, ils le font avec les annotations, mais je pense que d'autres FW le font avec la doc pour PHP, ce langage n'ayant pas les annotations) mais perso, je déteste, ça supprime souvent la customisation et surtout à mon sens, le code est ce que la machine exécute et la doc sert à l'humain à comprendre plus facilement ce code... Là, en mixant les deux, les points d'arrêts passent à côté de la doc alors qu'elle influencerait l'exécution :/

'fin bon, les routes, c'est pas le soucis puisqu'elles matchent directement le dossier du endpoint (/map/world/ c'est la route de... php/eclerd/endpoint/map/world/) Ce qui me bloquait plus, c'étaient les typages des paramètres, leur caractère obligatoire, et les réponses du endpoint, parce que là, c'est pas simple à extraire (que ce soit code=>doc ou doc=>code)


class MapMapcaseConstructionConstructionSubmitEndpoint extends AWebSimpleFormEndpoint {
public const PARAM_ID_MAPCASE = 'idMapcase';
public const PARAM_ID_BUILDING = 'idBuilding';

protected function httpPost(IConfig $config): IWebUrlResponder {
try {
$bean = PdoProcedure::call(
$config->getDb(),
$config->getSessionManager()->mustBeLoggedIn(),
Param::requiredPostId(static:10ARAM_ID_MAPCASE)->getValue(),
Param::requiredPostId(static:10ARAM_ID_BUILDING)->getValue());
return TitlemessageTemplate::success(
"Chantier lancé",
"Le chantier \"" . BuildingHtml::getLabel($bean->building->id) . "\" a été lancé.");
} catch (NotAllowedSqlException $e) {
return TitlemessageTemplate::forbidden(
"Action impossible",
"Vous ne pouvez pas construire ce bâtiment sur cette case.");
}
}
}

Là, il faudra que je trouve comment déterminer les typages des paramètres, le fait qu'ils soient obligatoires, le fait que le endpoint réponde HTTP 200 ou HTTP 403, quand est-ce qu'il répond 403, et quels formats de réponse il accepte (aka les formats de TitlemessageTemplate)... Pour l'instant, j'ai donc laissé tomber, c'est pas d'une importance capitale. Après, si t'as des suggestions, je suis preneur 16 Mon autre piste étaient de faire un test par endpoint et d'y checker ses capacités de formattage, mais c'est lourd je trouve...
Répondre




Utilisateur(s) parcourant ce sujet : 1 visiteur(s)