{"action":"opened","issue":{"url":"https://api.github.com/repos/funkywork/RME/issues/41","labels_url":"https://api.github.com/repos/funkywork/RME/issues/41/labels{/name}","comments_url":"https://api.github.com/repos/funkywork/RME/issues/41/comments","events_url":"https://api.github.com/repos/funkywork/RME/issues/41/events","html_url":"https://github.com/funkywork/RME/issues/41","id":54665068,"number":41,"title":"Redesign de la documentation","user":{"login":"Grimimi","id":6772534,"avatar_url":"https://avatars.githubusercontent.com/u/6772534?v=3","gravatar_id":"","url":"https://api.github.com/users/Grimimi","html_url":"https://github.com/Grimimi","followers_url":"https://api.github.com/users/Grimimi/followers","following_url":"https://api.github.com/users/Grimimi/following{/other_user}","gists_url":"https://api.github.com/users/Grimimi/gists{/gist_id}","starred_url":"https://api.github.com/users/Grimimi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Grimimi/subscriptions","organizations_url":"https://api.github.com/users/Grimimi/orgs","repos_url":"https://api.github.com/users/Grimimi/repos","events_url":"https://api.github.com/users/Grimimi/events{/privacy}","received_events_url":"https://api.github.com/users/Grimimi/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/funkywork/RME/labels/enhancement","name":"enhancement","color":"84b6eb"}],"state":"open","locked":false,"assignee":null,"milestone":null,"comments":0,"created_at":"2015-01-17T15:39:59Z","updated_at":"2015-01-17T15:39:59Z","closed_at":null,"body":"Bonjour à tous. Premièrement, j'aimerais vraiment vous féliciter pour le travail titanesque que vous avez produit, surtout @Jauke  et @xvw . Je suis assez impressionné par la qualité et les perspectives proposées, je trouve que c'est un bien bel \"event extender 1987768\". \r\n\r\nJ'arrive donc, un peu, en inspecteur des travaux finis pour évoquer quelques petits points. Initialement, la module de documentation de RME a été pensé, IMHO (il faudrait demander à @hyperaho si je ne me trompe pas) pour être lié à un préprocesseur, capable de générer la documentation, pas pour être utiliser de manière brute. Car soyons objectif, la routine de description est beaucoup trop verbeuse !\r\n\r\nPar exemple : \r\n\r\n```ruby\r\nclass Fixnum\r\n  #--------------------------------------------------------------------------\r\n  # * Documentation\r\n  #--------------------------------------------------------------------------\r\n  link_class_documentation \"Extension des entiers\"\r\n  link_method_documentation :\"self.to_digit\",  \r\n                            \"Renvoie la représentation textuelle d'un chiffre\",\r\n                            {}, true\r\n\r\n  link_snippet \"self.to_digit\", \"9.to_digit # :nine\"\r\nend\r\n```\r\n\r\nMême s'il est vrai que ça semble tout à fait acceptable (même si un peu long), je remarque la difficulté à distinguer des méthodes du singleton et des méthodes classiques : \r\n\r\n```ruby\r\n# Singleton \r\nlink_method_documentation \"Keyboard.trigger?\"\r\n# Classic\r\nlink_method_documentation \"trigger?\"\r\n\r\n# WTF !\r\n```\r\n\r\nMême si la distinction est compréhensible, je trouve que ce n'est pas du tout propre, car on délègue à une chaine de caractère, la distinction de scope. \r\nDans le cas des commandes, c'est encore pire car il faut impérativement enregistrer la commande, décrire sa catégorie etc. C'est relou ! \r\n\r\nJe ne serai sans doute pas intervenu si je n'avais pas lorgné les reporting csv/tsv que RME fait, qui sont honteusement plus ingénieux et ne demande aucune description ! \r\nJ'interviens donc pour proposer ma mise à jours ! \r\n\r\n## Séparation\r\nPremièrement, je trouve que l'usage d'une même sémantique (à une commande près) n'est pas une bonne approche et je préfère délocaliser totalement la documentation du code pour me focaliser sur les commandes. \r\nPourquoi? \r\nJe pense qu'une documentation des composantes internes est une très bonne chose, mais drastiquement complexe à mettre en oeuvre ! Chaque nouveau constituant doit être documenté, ce qui n'est pas le cas actuellement et qui risque de dégommer la courbe de développement, actuellement très pentue ! \r\nDonc je propose une **séparation**. Déléguer la documentation à un outil comme [RDoc](https://github.com/rdoc/rdoc) et qui serait exclusivement en anglais et composée via des commentaires. Pour être facile à mettre en oeuvre et profiter d'un output uniforme ! (Et surtout, pour le moment on peut s'en ficher :D).\r\nEt repenser la syntaxe de la documentation d'une commande qui ne serait plus délocalisée dans un fichier Doc.rb mais dans les modules de définitions de commandes. \r\n\r\n## Workflow actuel de la définition de commande \r\n*   Implémentation d'un module \r\n*   Implémentation des commandes \r\n*   Utilisation de `append_commands`\r\n*   Documentation dans Doc.rb ce qui implique \r\n    *  Définition d'une catégorie si besoin \r\n    *  Description de la commande\r\n    *  Description de ses arguments (et de son retour)\r\n    *  Enregistrement dans la commande dans la bonne catégorie.\r\nHeureusement, les ajouts récents dans DocGenerator.rb simplifient ce Workflow mais dans l'idée générale, c'est ceci !\r\n\r\n## Problématique soulevée (et pistes) \r\nL'écriture d'une documentation est une phase de la programmation qui vise à penser le design de son application. L'écrire \"après\" l'implémentation devient donc une tâche douloureuse! Je préfère donc imaginer décrire d'abord la documentation et ensuite implémenter les commandes. Pour ça il faut une manière expressive de déclarer la documentation d'une fonction, sans perdre l'objet Method relatif. \r\n\r\nUn autre soucis est la répétition de la catégorie. Techniquement, une catégorie est une unité de calcul finie (mais extensible), ce qui est le cas d'un module. Il faudrait, donc de mon point de vue, considérer le module parent comme une catégorie, pour laisser la position d'une commande dans un module comme un outil suffisant pour en déduire sa catégorie !\r\n\r\nTroisième partie, les arguments, l'argument `picture_id` ou encore `x` et `y` est répété une centaine de fois. Voyons comment il est possible d'améliorer tout ça :+1: \r\n\r\n## Design proposé \r\n\r\nVoici un exemple :8ball: \r\n\r\n```ruby\r\n# -*- coding: utf-8 -*-\r\n#==============================================================================\r\n# ** RME V1.0.0 Commands\r\n#------------------------------------------------------------------------------\r\n#  With : \r\n# Grim (original project)\r\n# Nuki \r\n# Raho\r\n#  Help :\r\n# Fabien\r\n# Zeus81\r\n# Joke \r\n# Zangther\r\n#------------------------------------------------------------------------------\r\n# Commands API\r\n# Provide all commands of the EvEx\r\n#==============================================================================\r\n\r\n# Enregistrement des arguments récurrents \r\nmodule RME::Doc\r\n  recurrent_arg :filename, :req, \"Nom du fichier (extension non obligatoire)\", :String\r\n  recurrent_arg :picture_id, :req, \"Identifiant de l'image\", :Fixnum\r\n  recurrent_arg :x, :req, \"Position X\", :Fixnum \r\n  recurrent_arg :y, :req, \"Position Y\", :Fixnum \r\n  recurrent_arg :opacity, :req, \"Opacité (entre 0 et 255)\", :Fixnum \r\n  recurrent_arg :blend_type, :req, \"Mode de fusion (0 pour normal, 1 pour addition, 2 pour soustraction\", :Fixnum\r\nend\r\n\r\n# Module de manipulation des images, factice, pour l'exemple \r\nmodule Pictures\r\n\r\n  # Documentation \r\n  spec do \r\n\r\n    desc \"Module de manipulation des images\"\r\n    method :picture_show, \"Affiche une image selon certaines coordonnées\"\r\n    method :picture_erase, \"Efface l'image référencée par son ID\"\r\n    method :picture_position, \"Modifie la position d'une image référencée par son ID\"\r\n\r\n  end \r\n\r\n  # Implémentation des commandes\r\n  def picture_show(picture_id, filename, x, y, opacity, blend_type)\r\n    #Code pour afficher une image\r\n  end\r\n\r\n  def picture_erase(picture_id)\r\n    # Code pour effacer une image \r\n  end\r\n\r\n  def picture_position(picture_id, x, y)\r\n    # Code pour modifier la position x/y d'une image\r\n  end\r\n\r\n\r\n  append_commands\r\nend\r\n\r\np Pictures.get_doc(:picture_show)\r\n\r\n```\r\n\r\nA noter qu'il est évidemment possible de créer des specs en inline. Par exemple : \r\n\r\n```ruby\r\n  spec :picture_erase, \"Efface une image\"\r\n  def picture_erase(picture_id)\r\n    # Code pour effacer une image \r\n  end\r\n```\r\n\r\nLes arguments sont déduits grâce à l'enregistrement des arguments récurrents ! Cependant, il est évidemment possible de surcharger un argument : \r\n\r\n```ruby\r\n spec :picture_erase, \"Efface une image\", [(arg :picture_id, :req, \"Un identifiant au hasard !!\", :String)]\r\n  def picture_erase(picture_id)\r\n    # Code pour effacer une image \r\n  end\r\n```\r\nSoit, il est possible de spécialiser une liste en dernier argument contenant une réécriture pour les arguments, permettant de traiter les cas particuliers. Si le code ne trouve aucun argument cohérent, il considère que la features est non documentée et présenter un argument de type \"polymorphe\", \"undocumented\". \r\n\r\nAu travers de ces outils, il est possible de documenter facilement n'importe quel cas de figure. \r\n\r\n*   `req` signifiant \"argument régulier\"\r\n*   `opt` signifiant \"argument optionnel\"\r\n*   `rest` signifiant \"reste des arguments\". \r\n\r\n## Code : \r\n\r\nRemplacement de la doc du SDK par : \r\n\r\n```ruby\r\n#==============================================================================\r\n  # ** RMEDoc\r\n  #------------------------------------------------------------------------------\r\n  # Doc\r\n  #==============================================================================\r\n\r\n  module Doc \r\n\r\n    #--------------------------------------------------------------------------\r\n    # * Singleton\r\n    #--------------------------------------------------------------------------\r\n\r\n    ARGS = Struct.new(:name, :kind, :desc, :type)\r\n    METH = Struct.new(:name, :desc, :args)\r\n\r\n    class << self\r\n\r\n      attr_accessor :interfaces\r\n      Doc.interfaces ||= Hash.new\r\n\r\n      attr_accessor :args\r\n      Doc.args ||= Hash.new\r\n\r\n      def recurrent_arg(name, kind, desc, type) \r\n        Doc.args[name.to_sym] = ARGS.new(name, kind, desc, type)\r\n      end\r\n\r\n    end\r\n\r\n    #--------------------------------------------------------------------------\r\n    # * Interface representation\r\n    #--------------------------------------------------------------------------\r\n\r\n    class Interface\r\n\r\n      #--------------------------------------------------------------------------\r\n      # * Public instance variables\r\n      #--------------------------------------------------------------------------\r\n\r\n      attr_accessor :classname\r\n      attr_accessor :methods\r\n      attr_accessor :description\r\n\r\n      #--------------------------------------------------------------------------\r\n      # * Interface constructor\r\n      #--------------------------------------------------------------------------\r\n      def initialize(klass)\r\n        @classname = klass.to_sym\r\n        @const = Object.const_get(@classname)\r\n        @methods = Hash.new\r\n      end\r\n\r\n      #--------------------------------------------------------------------------\r\n      # * Describe method\r\n      #--------------------------------------------------------------------------\r\n      def add_method(name, desc, args = [])\r\n        @methods[name.to_sym] = METH.new(name, desc, args)\r\n      end\r\n      alias_method :method, :add_method\r\n      alias_method :desc, :description=\r\n\r\n      #--------------------------------------------------------------------------\r\n      # * Retreive method\r\n      #--------------------------------------------------------------------------\r\n      def arg(name, kind, desc, type)\r\n        ARGS.new(name, kind, desc, type)\r\n      end\r\n      def retreive(meth)\r\n        m = @const.method(meth) \r\n        unk = ->(n, k) { ARGS.new(n, k, \"Unknown parameter\", :Unknown) }\r\n        current = METH.new(meth, \"Undocumented command\", [])\r\n        unless @methods[meth]\r\n          current.args = m.parameters.collect do |param|\r\n            kind, name = *param\r\n            RME::Doc.args[name] || unk.call(name, kind) \r\n          end\r\n          return current\r\n        end\r\n        current.desc = @methods[meth].desc\r\n        args = Array.new\r\n        m.parameters.each do |param|\r\n          kind, name = *param\r\n          arg = @methods[meth].args.find {|e|e.name == name}\r\n          if arg then args << arg\r\n          else args << (RME::Doc.args[name] || unk.call(name, kind))\r\n          end\r\n        end\r\n        current.args = args\r\n        return current\r\n      end\r\n\r\n    end\r\n\r\n    #--------------------------------------------------------------------------\r\n    # * Interface API\r\n    #--------------------------------------------------------------------------\r\n    module Spec \r\n\r\n\r\n      def arg(name, kind, desc, type)\r\n        RME::Doc::ARGS.new(name, kind, desc, type)\r\n      end\r\n      #--------------------------------------------------------------------------\r\n      # * interface function\r\n      #--------------------------------------------------------------------------\r\n      def spec(*args, &block)\r\n        self.send(:extend, self)\r\n        klass = \"#{self}\".to_sym\r\n        RME::Doc.interfaces[klass] ||= Interface.new(klass)\r\n        unless block_given?\r\n          if args.length == 1\r\n            RME::Doc.interfaces[klass].desc(args[0])\r\n          elsif [2,3].include?(args.length)\r\n            RME::Doc.interfaces[klass].method(*args)\r\n          end  \r\n          return\r\n        end\r\n        RME::Doc.interfaces[klass].instance_eval(&block)\r\n      end\r\n\r\n      #--------------------------------------------------------------------------\r\n      # * Get documented function\r\n      #--------------------------------------------------------------------------\r\n      def get_doc(meth)\r\n        klass = \"#{self}\".to_sym\r\n        RME::Doc.interfaces[klass].retreive(meth)\r\n      end\r\n\r\n    end\r\n\r\n\r\n  end\r\n\r\n```\r\n\r\n## Futur ? \r\nSi ces features vous plaisent ! (n'hésitons pas a en parler), implémenter une spec \"internal\" pour considérer un ajout comme interne (et ne pas le documenter), recoder toutes les méthodes avec la bonne sémantique et ET créer le docGenerator :)\r\n\r\n"}}