PY-11855 Run manage.py task improvements
authorIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Thu, 29 Jan 2015 13:14:46 +0000 (16:14 +0300)
committerIlya.Kazakevich <Ilya.Kazakevich@jetbrains.com>
Thu, 29 Jan 2015 13:14:46 +0000 (16:14 +0300)
Infrastructure created to fetch commands from python executing real commands

python/helpers/pycharm/_django_obtainer_core.py [new file with mode: 0644]
python/helpers/pycharm/_django_obtainer_optparse.py [new file with mode: 0644]
python/helpers/pycharm/django_obtainer.py [new file with mode: 0644]
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/Command.java
python/src/com/jetbrains/python/commandInterface/commandsWithArgs/InCommandStrategy.java

diff --git a/python/helpers/pycharm/_django_obtainer_core.py b/python/helpers/pycharm/_django_obtainer_core.py
new file mode 100644 (file)
index 0000000..0ef2d0d
--- /dev/null
@@ -0,0 +1,134 @@
+# coding=utf-8
+"""
+TODO: Support real help (from show_help()), not only help text
+This module exports information about manage commands and options from django to PyCharm.
+Information is provided in XML (to prevent encoding troubles and simplify deserialization on java side).
+It does not have schema (yet!) but here is XML format it uses.
+
+<commandInfo-array> -- root
+<commandInfo args="args description" help="human readable text" name="command name"> -- info about command
+<option help="option help" numberOfArgs="number of values (nargs)" type="option type: Option.TYPES"> -- one entry for each option
+<longNames>--each-for-one-long-opt-name</longNames>
+<shortNames>-each-for-one-short-name</shortNames>
+</option>
+</commandInfo>
+</commandInfo-array>
+
+
+
+"""
+from distutils.version import StrictVersion
+from xml.dom import minidom
+from xml.dom.minidom import Element
+import django
+import _django_obtainer_optparse
+
+__author__ = 'Ilya.Kazakevich'
+
+
+class XmlDumper(object):
+    """"
+    Creates an API to generate XML provided in this package.
+    How to use:
+    * dumper.start_command(..)
+    * dumper.add_command_option(..) # optional
+    * dumper.close_command()
+    * print(dumper.xml)
+
+    """
+
+    __command_info_tag = "commandInfo"  # Name of main tag
+
+    def __init__(self):
+        super().__init__()
+        self.__document = minidom.Document()
+        self.__root = self.__document.createElement("{0}-array".format(XmlDumper.__command_info_tag))
+        self.__document.appendChild(self.__root)
+        self.__command_element = None
+
+    def __create_text_array(self, parent, tag_name, values):
+        """
+        Creates array of text elements and adds them to parent
+
+        :type parent Element
+        :type tag_name str
+        :type values list of str
+
+        :param parent destination to add new elements
+        :param tag_name name tag to create to hold text
+        :param values list of values to add
+
+        """
+        for value in values:
+            tag = self.__document.createElement(tag_name)
+            text = self.__document.createTextNode(value)
+            tag.appendChild(text)
+            parent.appendChild(tag)
+
+    def start_command(self, command_name, command_help_text, command_args_text):
+        """
+        Starts manage command
+
+        :param command_name: command name
+        :param command_help_text: command help
+        :param command_args_text: command text for args
+
+
+        """
+        assert not bool(self.__command_element), "Already in command"
+        self.__command_element = self.__document.createElement(XmlDumper.__command_info_tag)
+        self.__command_element.setAttribute("name", command_name)
+        self.__command_element.setAttribute("help", command_help_text)
+        self.__command_element.setAttribute("args", command_args_text)
+        self.__root.appendChild(self.__command_element)
+
+    def add_command_option(self, opt_type, choices, long_opt_names, short_opt_names, help_text, num_of_args):
+        """
+        Adds command option
+
+        :param opt_type: "string", "int", "long", "float", "complex", "choice"
+        :param choices: list of choices for "choice" type
+        :param long_opt_names:  list of long opt names
+        :param short_opt_names: list of short opt names
+        :param help_text: help text
+        :param num_of_args: number of arguments
+
+        :type opt_type str
+        :type choices list of string
+        :type long_opt_names list of str
+        :type short_opt_names list of str
+        :type help_text str
+        :type num_of_args int
+        """
+        assert isinstance(self.__command_element, Element), "Add option in command only"
+        option = self.__document.createElement("option")
+        option.setAttribute("type", opt_type)
+
+        if choices:
+            self.__create_text_array(option, "choices", choices)
+        if long_opt_names:
+            self.__create_text_array(option, "longNames", long_opt_names)
+        if short_opt_names:
+            self.__create_text_array(option, "shortNames", short_opt_names)
+
+        option.setAttribute("help", help_text)
+        if num_of_args:
+            option.setAttribute("numberOfArgs", str(num_of_args))
+        self.__command_element.appendChild(option)
+
+    def close_command(self):
+        """
+        Closes currently opened command
+        """
+        assert bool(self.__command_element), "No command to close"
+        self.__command_element = None
+        pass
+
+    @property
+    def xml(self):
+        """
+
+        :return: current commands as XML as described in package
+        :rtype str
+        """
+        return self.__document.toprettyxml()
diff --git a/python/helpers/pycharm/_django_obtainer_optparse.py b/python/helpers/pycharm/_django_obtainer_optparse.py
new file mode 100644 (file)
index 0000000..e356697
--- /dev/null
@@ -0,0 +1,36 @@
+# coding=utf-8
+"""
+Exports data from optparse-based manage.py commands and reports it to pycharm.django_manage_obtainer._XmlDumper
+"""
+from optparse import Option
+from django.core.management import ManagementUtility, get_commands, BaseCommand
+
+__author__ = 'Ilya.Kazakevich'
+
+
+def report_data(dumper):
+    """
+    Fetches data from manage.py commands and reports it to dumper.
+
+    :type dumper _django_obtainer_core_XmlDumper
+    :param dumper: destination to report
+    """
+    utility = ManagementUtility()
+    for command_name in get_commands().keys():
+        command = utility.fetch_command(command_name)
+        assert isinstance(command, BaseCommand)
+        dumper.start_command(command_name=command_name,
+                             command_help_text=str(command.help),
+                             command_args_text=str(command.args))
+        for opt in command.option_list:
+            opt_type = opt.type if opt.type in Option.TYPES else ""  # Empty for unknown
+            # There is no official way to access this field, so I use protected one. At least it is public API.
+            # noinspection PyProtectedMember
+            dumper.add_command_option(
+                opt_type=opt_type,
+                choices=opt.choices,
+                long_opt_names=opt._long_opts,
+                short_opt_names=opt._short_opts,
+                help_text=opt.help,
+                num_of_args=opt.nargs)
+        dumper.close_command()
diff --git a/python/helpers/pycharm/django_obtainer.py b/python/helpers/pycharm/django_obtainer.py
new file mode 100644 (file)
index 0000000..15bf56c
--- /dev/null
@@ -0,0 +1,20 @@
+# coding=utf-8
+"""
+This XML is part of API implemented by XmlDumper, but data fetch engine is optparse-specific (at least in Django <1.8)
+and implemented in _django_obtainer_optparse.py.
+
+Module to be call package directly, just to get XML, but be sure env var DJANGO_SETTINGS_MODULE is set to something
+like "mysite.settings"
+"""
+from distutils.version import LooseVersion
+import django
+import _django_obtainer_optparse
+from _django_obtainer_core import XmlDumper
+
+__author__ = 'Ilya.Kazakevich'
+
+# TODO: Support Django 1.8 as well, it uses argparse, not optparse
+assert LooseVersion(django.get_version()) < LooseVersion('1.8a'), "Only Django <1.8 is supported now"
+dumper = XmlDumper()
+_django_obtainer_optparse.report_data(dumper)
+print(dumper.xml)
\ No newline at end of file
index 38b526e139f109ba0a23680700aa1fb91ea2eb33..0104d4e8c300a738a59bd742b1d3414228a3b8e8 100644 (file)
 package com.jetbrains.python.commandInterface.commandsWithArgs;
 
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.*;
 
 /**
  * Command
+ * TODO: add args
  *
  * @author Ilya.Kazakevich
  */
-public class Command {
+public final class Command {
   @NotNull
   private final String myName;
   @NotNull
   private final List<Argument> myArguments = new ArrayList<Argument>();
+  @Nullable
+  private final String myHelp;
 
   /**
+   * @param help help text
    * @param name      command name
    * @param arguments command arguments
    */
-  public Command(@NotNull final String name, @NotNull final Argument... arguments) {
-    this(name, Arrays.asList(arguments));
+  public Command(@NotNull final String name, @Nullable final String help, @NotNull final Argument... arguments) {
+    this(name, help, Arrays.asList(arguments));
   }
 
-  public Command(@NotNull final String name, @NotNull final Collection<Argument> arguments) {
+  /**
+   * @param help help text
+   * @param name      command name
+   * @param arguments command arguments
+   */
+  public Command(@NotNull final String name, @Nullable final String help, @NotNull final Collection<Argument> arguments) {
     myName = name;
     myArguments.addAll(arguments);
+    myHelp = help;
   }
 
   /**
@@ -58,4 +69,9 @@ public class Command {
   List<Argument> getArguments() {
     return Collections.unmodifiableList(myArguments);
   }
+
+  @Nullable
+  public String getHelp() {
+    return myHelp;
+  }
 }
index c7a84377addf3be0917142892de50f3f79660904..684aa733349350e1a3836fa4e1131f6940e07f6b 100644 (file)
@@ -49,7 +49,11 @@ class InCommandStrategy extends Strategy {
   @NotNull
   @Override
   public String getSubText() {
-    return "Tab will display list of arguments in next commit";
+    final String help = myCommand.getHelp();
+    if (help != null) {
+      return help;
+    }
+    return "Place to display help";
   }
 
   @NotNull