Nous publions aujourd’hui une nouvelle version majeure du projet CSV Parser pour Node.js. La version 4 est une réécriture complète du projet axée sur la performance. Il comporte également de nouvelles fonctionnalités, ainsi que des amélioriation dans les options et les informations exportées. Le site officiel est mis à jour et le Changelog contient la liste des modifications apportées à cette version majeure.

Un travail massif

Le projet CSV de Node.js a été lancé le 25 septembre 2010. C’est assez ancien dans ce monde de la tech en forte évolution. Depuis lors, il a survécu à plusieurs évolutions de Node.js, telles que les refontes de l’API Stream. Au fil des ans, le projet a été maintenu par de la correction de bugs, de la documentation et du support. Avec l’aide de la communauté, des fonctionnalités supplémentaires ont été fournies pour répondre aux besoins de chacun. La qualité de la suite de tests nous a conforté dans notre confiance pour se replonger régulièrement dans le code et y apporter de nouvelles fonctionnalités. Cependant, il y avait une tâche que je n’avais jamais eu le courage d’initier: réécrire le coeur du parseur pour tirer parti de l’API Buffer et de ses promesses de performances. Quelques jours de vacances m’ont permis d’engager ce travail.

La réécriture a commencé avec un nouveau projet vierge. Bien qu’il y ait probablement encore de la place pour des améliorations et des optimisations supplémentaires, je lancé plusieurs études comparatives pour mesurer l’impact de plusieurs implémentations sur les performances. C’est ainsi que j’ai créé la classe Resizable Buffer, qui réutilise le même Buffer interne en s’adaptant au jeu de données d’entrée au lieu d’instancier un nouveau tampon pour chaque champ. Une fois prêt, l’étape suivante consistait à écrire l’analyseur. Le processus a été divisé en plusieurs itérations, 13 exactement:

  1. Itération naîve sur le Buffer d’entrée
  2. Ajout de __needMoreData
  3. Ajout de __autoDiscoverRowDelimiter
  4. Première ébauches des options quote , escape , delimiter, et record_delimiter
  5. Finalisation des options quote , escape , delimiter  et record_delimiter
  6. Option comment
  7. Options relax_column_count  et skip_empty_lines ainsi que les information count , empty_line_count  et skipped_line_count
  8. Options skip_lines_with_empty_values , skip_lines_with_error , from , to
  9. Option columns
  10. Option trim
  11. Option relax
  12. Options objname , raw , cast, et cast_date
  13. Réécriture des compteur de l’objet info

L’implémentation n’utilise plus CoffeeScript et est écrite directement sous JavaScript 6. Ne vous méprenez pas, je suis toujours un grand fan de CoffeeScript et nous l’utilisons encore dans les tests pour son expressivité. Cependant, j’avais besoin d’un contrôle précis du code et utiliser JavaScript comme langue principale encouragera, espérons-le, davantage de contributions.

Évolutions entraînant des incompatibilités

Dans l’ensemble, il n’y a pas de changements majeurs. Les modules sont les mêmes et l’API pour les utiliser est restée inchangée. Quelques modifications mineures sont toutefois à prendre en compte, telles que l’option rowDelimiter  désormais renommée en record_delimiter, certaines options précédemment dépréciées maintenant supprimées et les compteurs disponibles regroupés dans la nouvelle propriété info  :

  • Option rowDelimiter devient record_delimiter
  • Drop the record  event
  • Normalisation des messages d’erreur {error type}: {error description}
  • Isolation des information de compteur dans l’objet info
  • count devient info.records
  • lines devient info.lines
  • empty_line_count  devient info.empty_lines
  • skipped_line_count  devient info.invalid_field_length
  • context.count  dans la function cast  devient context.records
  • Suppression des options dépréssiées auto_parse  and auto_parse_date
  • Suppression de l’évènement record
  • Dans l’option raw, la propriété row  est renommée record
  •   max_limit_on_data_read  devient max_record_size
  • Valeur par défault de max_record_size  devient 0 (pour illimité)

Le changement le plus important est probablement celui concernant l’option rowDelimiter  en record_delimiter  en raison de sa forte utilisation. De plus, max_record_size  est maintenant illimité par défaut et doit être explicitement défini s’il est utilisé.

Nouvelles fonctionnalités

Cette nouvelle version comprend également de nouvelles fonctionnalités. Le nouvel objet d’information regroupe quelques propriétés de compteur anciennement disponibles directement à partir de l’instance du parseur. Ces propriétés ont été renommées pour être plus expressives. L’objet d’information est directement disponible à partir de l’instance du parseur en tant que info. Pour les utilisateurs de callback, cet object est exporté en tant que troisième argument de la fonction. Ils peuvent également être disponibles pour chaque entrée en activant l’option info  avec la valeur true.

Cette version introduit 3 nouvelles options que sont info , from_line  et to_line :

  • info : génère deux propriétés info  et record, info étant un instantané de l’objet info  au moment de la création d’une entrée, sous forme d’array ou d’objet; remarque, il peut être utilisé conjointement avec l’option raw.
  • from_line : commence le traitement des entrées à partir de la ligne donnée.
  • to_line : arrête le traitement des entrées après la ligne donnée.

L’option info  est utile pour débuguer ou donner aux utilisateurs finaux des informations sur leur erreur.

Les options from_line  et to_line  filtrent respectivement les première et dernière lignes d’un ensemble de données. En parlant de lignes, les versions précédentes du parseur étaient confuses quand il s’agissait de compter des lignes et des délimiteurs d’entrées. Cela fonctionnait pour la plupart des utilisateurs pour la bonne raison qu’ils sont généralement égaux. Cette nouvelle version corrige le problème.

Voici la liste des nouvelles fonctionnalités extraites du Changelog:

  • Nouvelles options info , from_line  et to_line
  • trim: respecte ltrim  et rtrim  lorsque définies
  • delimiter: accepte un Buffer
  • delimiter: supporte plusieurs bytes/characters
  • callback: exporte l’objet info  en tant que 3ème argument
  • cast: attrape les erreurs dans les fonctions utilisateurs
  • TypeScript: définie les propriétés d' info  comme étant en lecture seule et toujours présentes
  • comment_lines : compte le nombre de lignes entièrement commentés (sans données)

Prochaines évolutions

Le validité du code source est garanti par une large suite de tests. Aucun test n’a été supprimé et de nouveaux tests sont apparus pour renforcer les garanties de l’analyseur. Il est toutefois possible que certains tests ne traitent pas de certains comportements et, dans les semaines à venir, nous comptons sur vos retours pour résoudre les éventuels problèmes.

Bien que n’étant pas un grand fan des Promise ES6 dans le contexte de l’analyseur, la demande a été faite à plusieurs reprises et n’est pas bien compliqué à mettre en oeuvre. Il sera également implémenté dans les autres packages CSV.

Une autre amélioration potentielle consiste à étendre les objets d’erreur avec des informations supplémentaires telles qu’un code unique associé à chaque type d’erreur. Bien qu’amélioré, le contenu des message mériterait à être encore normalisé.

Je prévois également de supporter Flow, le vérificateur de type statique. Je ne l’ai jamais utilisé auparavant. Son intégration me semble approprié et cela me donnera l’occasion de l’essayer.

Enfin, j’envisage d’écrire un outil en ligne de commande qui exposera toutes les options disponibles et fournira plusieurs formats de sortie (JSON, ligne JSON, YAML, …).


Also published on Medium.