Example #1
0
    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.accepted.connect(self.validate_entries)
        self.dialog.button_box.helpRequested.connect(self.show_help)

        # show the dialog
        self.dialog.show()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', self._last_used_path())
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        self._set_last_used_path(self.plugin_path)
        template_dir = self._create_plugin_directory()
        self._prepare_code(specification, template_dir)
        self._prepare_help(template_dir)
        self._prepare_tests(specification)

        self._prepare_scripts()

        #resource = QFile(os.path.join(template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.web_view.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()
    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # connect the ok button to our method
        self.dialog.button_box.accepted.connect(self.validate_entries)
        self.dialog.button_box.helpRequested.connect(self.show_help)

        # show the dialog
        self.dialog.show()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', '.')
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        template_dir = self._create_plugin_directory()
        self._prepare_code(specification, template_dir)
        self._prepare_help(template_dir)
        self._prepare_tests(specification)

        self._prepare_scripts()

        #resource = QFile(os.path.join(template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.web_view.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()
class PluginBuilder:
    """A QGIS plugin that allows you to build QGIS plugins."""
    def __init__(self, iface):
        """Constructor

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgisInterface

        """
        # Save reference to the QGIS interface
        self.iface = iface
        # noinspection PyArgumentList
        self.user_plugin_dir = QFileInfo(
            QgsApplication.qgisUserDbFilePath()).path() + 'python/plugins'
        self.plugin_builder_path = os.path.dirname(__file__)

        # class members
        self.action = None
        self.dialog = None
        self.plugin_path = None

    # noinspection PyPep8Naming
    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        # Create action that will start plugin configuration
        self.action = QAction(QIcon(':/plugins/plugin_builder/icon.png'),
                              'Plugin Builder', self.iface.mainWindow())
        # connect the action to the run method
        self.action.triggered.connect(self.run)

        # Add toolbar button and menu item
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu('&Plugin Builder', self.action)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.iface.removePluginMenu('&Plugin Builder', self.action)
        self.iface.removeToolBarIcon(self.action)

    def _get_plugin_path(self):
        """Prompt the user for the path where the plugin should be written to.
        """
        while not QFileInfo(self.plugin_path).isWritable():
            # noinspection PyTypeChecker,PyArgumentList
            QMessageBox.critical(None, 'Error', 'Directory is not writeable')
            # noinspection PyCallByClass,PyTypeChecker
            self.plugin_path = QFileDialog.getExistingDirectory(
                self.dialog, 'Select the Directory for your Plugin',
                self._last_used_path())
            if self.plugin_path == '':
                return False
        return True

    def _prepare_tests(self, specification):
        """Populate and write help files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        self.populate_template(specification, self.shared_dir,
                               'help/source/conf.py.tmpl',
                               'help/source/conf.py')
        self.populate_template(specification, self.shared_dir,
                               'help/source/index.rst.tmpl',
                               'help/source/index.rst')
        # copy the unit tests folder
        test_source = os.path.join(self.shared_dir, 'test')
        test_destination = os.path.join(self.plugin_path, 'test')
        copy(test_source, test_destination)

    def _prepare_scripts(self):
        """Copy the scripts folder."""
        scripts_source = os.path.join(self.shared_dir, 'scripts')
        copy(scripts_source, os.path.join(self.plugin_path, 'scripts'))

    def _prepare_i18n(self):
        """Copy the i18n folder."""
        scripts_source = os.path.join(self.shared_dir, 'i18n')
        copy(scripts_source, os.path.join(self.plugin_path, 'i18n'))

    def _prepare_help(self):
        """Prepare the help directory."""
        # Copy over pylintrc
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(os.path.join(self.shared_dir, 'pylintrc'),
                   os.path.join(self.plugin_path, 'pylintrc'))
        # Create sphinx default project for help
        QDir().mkdir(self.plugin_path + '/help')
        QDir().mkdir(self.plugin_path + '/help/build')
        QDir().mkdir(self.plugin_path + '/help/build/html')
        QDir().mkdir(self.plugin_path + '/help/source')
        QDir().mkdir(self.plugin_path + '/help/source/_static')
        QDir().mkdir(self.plugin_path + '/help/source/_templates')
        # copy doc makefiles
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(os.path.join(self.shared_dir, 'help/make.bat'),
                   os.path.join(self.plugin_path, 'help/make.bat'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(os.path.join(self.shared_dir, 'help/Makefile'),
                   os.path.join(self.plugin_path, 'help/Makefile'))

    def _prepare_code(self, specification):
        """Prepare the code turning templates into python.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        # process the user entries
        if specification.gen_makefile:
            self.populate_template(specification, self.shared_dir,
                                   'Makefile.tmpl', 'Makefile')
        if specification.gen_pb_tool:
            self.populate_template(specification, self.shared_dir,
                                   'pb_tool.tmpl', 'pb_tool.cfg')
        self.populate_template(specification, self.template_dir,
                               '__init__.tmpl', '__init__.py')
        self.populate_template(specification, self.template_dir,
                               'module_name.tmpl',
                               '%s.py' % specification.module_name)
        if specification.gen_scripts:
            release_script = QFile(os.path.join(self.shared_dir, 'release.sh'))
            release_script.copy(os.path.join(self.plugin_path, 'release.sh'))
            plugin_upload = QFile(
                os.path.join(self.shared_dir, 'plugin_upload.py'))
            plugin_upload.copy(
                os.path.join(self.plugin_path, 'plugin_upload.py'))
            # noinspection PyCallByClass,PyTypeChecker
            QFile.setPermissions(
                os.path.join(self.plugin_path,
                             'plugin_upload.py'), QFile.ReadOwner
                | QFile.WriteOwner | QFile.ExeOwner | QFile.ReadUser
                | QFile.WriteUser | QFile.ExeUser | QFile.ReadGroup
                | QFile.ExeGroup | QFile.ReadOther | QFile.ExeOther)

    def _prepare_specific_files(self, specification):
        """Prepare specific templates and files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        for template_name, output_name in \
                self.template.template_files(specification).iteritems():
            self.populate_template(specification, self.template_dir,
                                   template_name, output_name)

        # copy the non-generated files to the new plugin dir
        for template_file, output_name in \
                self.template.copy_files(specification).iteritems():
            file = QFile(os.path.join(self.template_dir, template_file))
            file.copy(os.path.join(self.plugin_path, output_name))

    def _prepare_readme(self, specification, template_module_name):
        """Prepare the README file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_module_name: Base name of the module for the new
            plugin.
        :type template_module_name: str
        """
        # populate the results readme text template
        template_file = open(os.path.join(self.template_dir, 'readme.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        # TODO: update this to simply pass the specification.template_map
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat':
            specification.template_map['TemplateVCSFormat']
        }
        popped = template.substitute(result_map)
        # write the results info to the README txt file
        readme_txt = codecs.open(
            os.path.join(str(self.plugin_path), 'README.txt'), 'w', 'utf-8')
        readme_txt.write(popped)
        readme_txt.close()

    def _prepare_metadata(self, specification):
        """Prepare metadata file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        metadata_file = codecs.open(
            os.path.join(str(self.plugin_path), 'metadata.txt'), 'w', 'utf-8')
        metadata_comment = (
            '# This file contains metadata for your plugin. Since \n'
            '# version 2.0 of QGIS this is the proper way to supply \n'
            '# information about a plugin. The old method of \n'
            '# embedding metadata in __init__.py will \n'
            '# is no longer supported since version 2.0.\n\n'
            '# This file should be included when you package your plugin.'
            '# Mandatory items:\n\n')
        metadata_file.write(metadata_comment)
        metadata_file.write('[general]\n')
        metadata_file.write('name=%s\n' % specification.title)
        metadata_file.write('qgisMinimumVersion=%s\n' %
                            specification.qgis_minimum_version)
        metadata_file.write('description=%s\n' % specification.description)
        metadata_file.write('version=%s\n' % specification.plugin_version)
        metadata_file.write('author=%s\n' % specification.author)
        metadata_file.write('email=%s\n\n' % specification.email_address)
        metadata_file.write('about=%s\n\n' % specification.about)
        metadata_file.write('tracker=%s\n' % specification.tracker)
        metadata_file.write('repository=%s\n' % specification.repository)
        metadata_file.write('# End of mandatory metadata\n\n')
        metadata_file.write('# Recommended items:\n\n')
        metadata_file.write(
            '# Uncomment the following line and add your changelog:\n')
        metadata_file.write('# changelog=\n\n')
        metadata_file.write('# Tags are comma separated with spaces allowed\n')
        metadata_file.write('tags=%s\n\n' % specification.tags)
        metadata_file.write('homepage=%s\n' % specification.homepage)
        metadata_file.write('category=%s\n' % self.template.category)
        metadata_file.write('icon=%s\n' % specification.icon)
        metadata_file.write('# experimental flag\n')
        metadata_file.write('experimental=%s\n\n' % specification.experimental)
        metadata_file.write(
            '# deprecated flag (applies to the whole plugin, not '
            'just a single version)\n')
        metadata_file.write('deprecated=%s\n\n' % specification.deprecated)
        metadata_file.close()

    def _prepare_results_html(self, specification):
        """Prepare results README.html file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        template_module_name = \
            specification.template_map['TemplateModuleName']
        template_file = open(os.path.join(self.template_dir, 'results.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat':
            specification.template_map['TemplateVCSFormat']
        }
        results_popped = template.substitute(result_map)
        # write the results info to the README HTML file
        readme = codecs.open(
            os.path.join(str(self.plugin_path), 'README.html'), 'w', 'utf-8')
        readme.write(results_popped)
        readme.close()
        return results_popped, template_module_name

    def _create_plugin_directory(self):
        """Create the plugin directory using the class name."""
        # remove spaces from the plugin name
        self.plugin_path = os.path.join(str(self.plugin_path),
                                        str(self.dialog.class_name.text()))
        QDir().mkdir(self.plugin_path)

    def _last_used_path(self):
        return QSettings().value('PluginBuilder/last_path', '.')

    def _set_last_used_path(self, value):
        QSettings().setValue('PluginBuilder/last_path', value)

    def _select_tags(self):
        tag_dialog = SelectTagsDialog()
        # if the user has their own taglist, use it
        user_tag_list = os.path.join(os.path.expanduser("~"),
                                     '.plugin_tags.txt')
        if os.path.exists(user_tag_list):
            tag_file = user_tag_list
        else:
            tag_file = os.path.join(str(self.plugin_builder_path),
                                    'taglist.txt')

        with open(tag_file) as tf:
            tags = tf.readlines()

        model = QStandardItemModel()

        for tag in tags:
            item = QStandardItem(tag[:-1])
            model.appendRow(item)

        tag_dialog.listView.setModel(model)
        tag_dialog.show()
        ok = tag_dialog.exec_()
        if ok:
            selected = tag_dialog.listView.selectedIndexes()
            seltags = []
            for tag in selected:
                seltags.append(tag.data())
            taglist = ", ".join(seltags)
            self.dialog.tags.setText(taglist)
        #QMessageBox.information(None, "Selection", seltags)

    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog(
            stored_output_path=self._last_used_path())

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.helpRequested.connect(self.show_help)
        self.dialog.select_tags.clicked.connect(self._select_tags)

        # show the dialog
        self.dialog.show()
        self.dialog.adjustSize()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = self.dialog.output_directory.text()

        self._set_last_used_path(self.plugin_path)
        self._create_plugin_directory()
        self.template = self.dialog.template()
        self.template_dir = os.path.join(self.template.subdir(), 'template')
        self.shared_dir = os.path.join(str(self.plugin_builder_path),
                                       'plugin_templates', 'shared')

        template_map = self.template.template_map(specification, self.dialog)
        specification.template_map.update(template_map)

        self._prepare_code(specification)
        if specification.gen_help:
            self._prepare_help()
        if specification.gen_tests:
            self._prepare_tests(specification)

        if specification.gen_scripts:
            self._prepare_scripts()

        if specification.gen_i18n:
            self._prepare_i18n()

        #resource = QFile(os.path.join(self.template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        self._prepare_specific_files(specification)

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.textBrowser.setOpenExternalLinks(True)
        results_dialog.textBrowser.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()

    def populate_template(self, specification, template_dir, template_name,
                          output_name):
        """Populate the template based on user data.

        :param specification: Descriptive data that will be used to create
            the plugin.
        :type specification: PluginSpecification

        :param template_dir: Directory where template is.
        :type template_dir: str

        :param template_name: Name for the template.
        :type template_name: str

        :param output_name:  Name of the output file to create.
        :type output_name: str
        """
        template_file_path = os.path.join(template_dir, template_name)
        output_name_path = os.path.join(self.plugin_path, output_name)

        template_file = open(template_file_path)
        content = template_file.read()
        template_file.close()
        template = Template(content)
        popped = template.substitute(specification.template_map)
        plugin_file = codecs.open(output_name_path, 'w', 'utf-8')
        plugin_file.write(popped)
        plugin_file.close()

    def show_help(self):
        """Display application help to the user."""
        help_file = 'file:///%s/help/index.html' % self.plugin_builder_path
        # For testing path:
        #QMessageBox.information(None, 'Help File', help_file)
        # noinspection PyCallByClass,PyTypeChecker
        QDesktopServices.openUrl(QUrl(help_file))
    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog(
            stored_output_path=self._last_used_path())

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.helpRequested.connect(self.show_help)
        self.dialog.select_tags.clicked.connect(self._select_tags)

        # show the dialog
        self.dialog.show()
        self.dialog.adjustSize()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = self.dialog.output_directory.text()

        self._set_last_used_path(self.plugin_path)
        self._create_plugin_directory()
        self.template = self.dialog.template()
        self.template_dir = os.path.join(self.template.subdir(), 'template')
        self.shared_dir = os.path.join(str(self.plugin_builder_path),
                                       'plugin_templates', 'shared')

        template_map = self.template.template_map(specification, self.dialog)
        specification.template_map.update(template_map)

        self._prepare_code(specification)
        if specification.gen_help:
            self._prepare_help()
        if specification.gen_tests:
            self._prepare_tests(specification)

        if specification.gen_scripts:
            self._prepare_scripts()

        if specification.gen_i18n:
            self._prepare_i18n()

        #resource = QFile(os.path.join(self.template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        self._prepare_specific_files(specification)

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.textBrowser.setOpenExternalLinks(True)
        results_dialog.textBrowser.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()
Example #5
0
class PluginBuilder:
    """A QGIS plugin that allows you to build QGIS plugins."""

    def __init__(self, iface):
        """Constructor

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface

        """
        # Save reference to the QGIS interface
        self.iface = iface
        # noinspection PyArgumentList
        self.user_plugin_dir = QFileInfo(
            QgsApplication.qgisUserDbFilePath()).path() + 'python/plugins'
        self.plugin_builder_path = os.path.dirname(__file__)

        # class members
        self.action = None
        self.dialog = None
        self.plugin_path = None

    # noinspection PyPep8Naming
    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        # Create action that will start plugin configuration
        self.action = QAction(
            QIcon(':/plugins/plugin_builder/icon.png'),
            'Plugin Builder', self.iface.mainWindow())
        # connect the action to the run method
        self.action.triggered.connect(self.run)

        # Add toolbar button and menu item
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu('&Plugin Builder', self.action)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.iface.removePluginMenu('&Plugin Builder', self.action)
        self.iface.removeToolBarIcon(self.action)

    def _get_plugin_path(self):
        """Prompt the user for the path where the plugin should be written to.
        """
        while not QFileInfo(self.plugin_path).isWritable():
            # noinspection PyTypeChecker,PyArgumentList
            QMessageBox.critical(
                None, 'Error', 'Directory is not writeable')
            # noinspection PyCallByClass,PyTypeChecker
            self.plugin_path = QFileDialog.getExistingDirectory(
                self.dialog,
                'Select the Directory for your Plugin',
                self._last_used_path())
            if self.plugin_path == '':
                return False
        return True

    def _prepare_tests(self, specification):
        """Populate and write help files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        self.populate_template(
            specification,
            'help/source/conf.py.tmpl', 'help/source/conf.py')
        self.populate_template(
            specification,
            'help/source/index.rst.tmpl', 'help/source/index.rst')
        # copy the unit tests folder
        test_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'test')
        test_destination = os.path.join(self.plugin_path, 'test')
        copy(test_source, test_destination)
        # These are templates so we don't copy it directly.
        os.remove(os.path.join(
            test_destination, 'test_module_name_dialog.templ'))
        os.remove(os.path.join(
            test_destination, 'test_resources.templ'))
        self.populate_template(
            specification,
            'test/test_module_name_dialog.templ',
            'test/test_%s_dialog.py' % specification.module_name)
        self.populate_template(
            specification,
            'test/test_resources.templ',
            'test/test_resources.py')

    def _prepare_scripts(self):
        """Copy the scripts folder."""
        scripts_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'scripts')
        copy(scripts_source, os.path.join(self.plugin_path, 'scripts'))
        # copy the i18n folder
        scripts_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'i18n')
        copy(scripts_source, os.path.join(self.plugin_path, 'i18n'))

    def _prepare_help(self, template_dir):
        """Prepare the help directory.

        :param template_dir: Directory where template is.
        :type template_dir: str
        """
        # Copy over pylintrc
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'pylintrc'),
            os.path.join(self.plugin_path, 'pylintrc'))
        # Create sphinx default project for help
        QDir().mkdir(self.plugin_path + '/help')
        QDir().mkdir(self.plugin_path + '/help/build')
        QDir().mkdir(self.plugin_path + '/help/source')
        QDir().mkdir(self.plugin_path + '/help/source/_static')
        QDir().mkdir(self.plugin_path + '/help/source/_templates')
        # copy doc makefiles
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'help/make.bat'),
            os.path.join(self.plugin_path, 'help/make.bat'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'help/Makefile'),
            os.path.join(self.plugin_path, 'help/Makefile'))

    def _prepare_code(self, specification, template_dir):
        """Prepare the code turning templates into python.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_dir: Directory where template is.
        :type template_dir: str
        """
        # process the user entries
        self.populate_template(
            specification, 'Makefile.tmpl', 'Makefile')
        self.populate_template(
            specification, 'pb_tool.tmpl', 'pb_tool.cfg')
        self.populate_template(
            specification, '__init__.tmpl', '__init__.py')
        self.populate_template(
            specification, 'module_name.tmpl',
            '%s.py' % specification.module_name)
        self.populate_template(
            specification, 'module_name_dialog.tmpl',
            '%s_dialog.py' % specification.module_name)
        self.populate_template(
            specification, 'module_name_dialog_base.ui.tmpl',
            '%s_dialog_base.ui' % specification.module_name)
        self.populate_template(
            specification, 'resources.tmpl', 'resources.qrc')
        # copy the non-generated files to the new plugin dir
        icon = QFile(os.path.join(template_dir, 'icon.png'))
        icon.copy(os.path.join(self.plugin_path, 'icon.png'))
        release_script = QFile(os.path.join(template_dir, 'release.sh'))
        release_script.copy(os.path.join(self.plugin_path, 'release.sh'))
        plugin_upload = QFile(
            os.path.join(template_dir, 'plugin_upload.py'))
        plugin_upload.copy(
            os.path.join(self.plugin_path, 'plugin_upload.py'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.setPermissions(
            os.path.join(self.plugin_path, 'plugin_upload.py'),
            QFile.ReadOwner | QFile.WriteOwner | QFile.ExeOwner |
            QFile.ReadUser | QFile.WriteUser | QFile.ExeUser |
            QFile.ReadGroup | QFile.ExeGroup | QFile.ReadOther |
            QFile.ExeOther)

    def _prepare_readme(self, specification, template_module_name):
        """Prepare the README file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_module_name: Base name of the module for the new
            plugin.
        :type template_module_name: str
        """
        # populate the results readme text template
        template_file = open(os.path.join(
            str(self.plugin_builder_path),
            'plugin_template',
            'readme.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        # TODO: update this to simply pass the specification.template_map
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        popped = template.substitute(result_map)
        # write the results info to the README txt file
        readme_txt = codecs.open(os.path.join(
            str(self.plugin_path), 'README.txt'), 'w', 'utf-8')
        readme_txt.write(popped)
        readme_txt.close()

    def _prepare_metadata(self, specification):
        """Prepare metadata file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        metadata_file = codecs.open(os.path.join(
            str(self.plugin_path), 'metadata.txt'), 'w', 'utf-8')
        metadata_comment = (
            '# This file contains metadata for your plugin. Since \n'
            '# version 2.0 of QGIS this is the proper way to supply \n'
            '# information about a plugin. The old method of \n'
            '# embedding metadata in __init__.py will \n'
            '# is no longer supported since version 2.0.\n\n'
            '# This file should be included when you package your plugin.'
            '# Mandatory items:\n\n')
        metadata_file.write(metadata_comment)
        metadata_file.write('[general]\n')
        metadata_file.write('name=%s\n' % specification.title)
        metadata_file.write(
            'qgisMinimumVersion=%s\n' % specification.qgis_minimum_version)
        metadata_file.write(
            'description=%s\n' % specification.description)
        metadata_file.write(
            'version=%s\n' % specification.plugin_version)
        metadata_file.write(
            'author=%s\n' % specification.author)
        metadata_file.write(
            'email=%s\n\n' % specification.email_address)
        metadata_file.write(
            '# End of mandatory metadata\n\n')
        metadata_file.write(
            '# Optional items:\n\n')
        metadata_file.write(
            '# Uncomment the following line and add your changelog:\n')
        metadata_file.write(
            '# changelog=\n\n')
        metadata_file.write(
            '# Tags are comma separated with spaces allowed\n')
        metadata_file.write(
            'tags=%s\n\n' % specification.tags)
        metadata_file.write(
            'homepage=%s\n' % specification.homepage)
        metadata_file.write(
            'tracker=%s\n' % specification.tracker)
        metadata_file.write(
            'repository=%s\n' % specification.repository)
        metadata_file.write(
            'icon=%s\n' % specification.icon)
        metadata_file.write(
            '# experimental flag\n')
        metadata_file.write(
            'experimental=%s\n\n' % specification.experimental)
        metadata_file.write(
            '# deprecated flag (applies to the whole plugin, not '
            'just a single version)\n')
        metadata_file.write('deprecated=%s\n\n' % specification.deprecated)
        metadata_file.close()

    def _prepare_results_html(self, specification):
        """Prepare results README.html file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        template_module_name = \
            specification.template_map['TemplateModuleName']
        template_file = open(os.path.join(
            str(self.plugin_builder_path),
            'plugin_template',
            'results.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        results_popped = template.substitute(result_map)
        # write the results info to the README HTML file
        readme = codecs.open(os.path.join(
            str(self.plugin_path), 'README.html'), 'w', 'utf-8')
        readme.write(results_popped)
        readme.close()
        return results_popped, template_module_name

    def _create_plugin_directory(self):
        """Create the plugin directory using the class name."""
        # remove spaces from the plugin name
        self.plugin_path = os.path.join(
            str(self.plugin_path),
            str(self.dialog.class_name.text()))
        QDir().mkdir(self.plugin_path)
        template_dir = os.path.join(
            str(self.plugin_builder_path), 'plugin_template')
        return template_dir

    def _last_used_path(self):
        return QSettings().value('PluginBuilder/last_path', '.')

    def _set_last_used_path(self, value):
        QSettings().setValue('PluginBuilder/last_path', value)


    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.accepted.connect(self.validate_entries)
        self.dialog.button_box.helpRequested.connect(self.show_help)

        # show the dialog
        self.dialog.show()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', self._last_used_path())
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        self._set_last_used_path(self.plugin_path)
        template_dir = self._create_plugin_directory()
        self._prepare_code(specification, template_dir)
        self._prepare_help(template_dir)
        self._prepare_tests(specification)

        self._prepare_scripts()

        #resource = QFile(os.path.join(template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.web_view.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()

    def validate_entries(self):
        """Check to see that all fields have been entered."""
        message = ''
        dialog = self.dialog
        if dialog.class_name.text() == '' or \
           dialog.title.text() == '' or \
           dialog.description.text() == '' or \
           dialog.module_name.text() == '' or \
           dialog.plugin_version.text() == '' or \
           dialog.qgis_minimum_version.text() == '' or \
           dialog.menu_text.text() == '' or \
           dialog.author.text() == '' or \
           dialog.email_address.text() == '':
                message = (
                    'Some required fields are missing. '
                    'Please complete the form.\n')
        try:
            # Assigning to _ is python sugar for a variable that will be unused
            _ = float(str(dialog.plugin_version.text()))
            _ = float(str(dialog.qgis_minimum_version.text()))
        except ValueError:
            message += 'Version numbers must be numeric.\n'
        # validate plugin name
        # check that we have only ascii char in class name
        try:
            unicode(dialog.class_name.text()).decode('ascii')
        except UnicodeEncodeError:
            dialog.class_name.setText(
                unicode(
                    dialog.class_name.text()).encode('ascii', 'ignore'))
            message += (
                'The Class name must be ASCII characters only, '
                'the name has been modified for you. \n')
        # check space and force CamelCase
        if str(dialog.class_name.text()).find(' ') > -1:
            class_name = capwords(str(dialog.class_name.text()))
            dialog.class_name.setText(class_name.replace(' ', ''))
            message += (
                'The Class name must use CamelCase. '
                'No spaces are allowed; the name has been modified for you.')
        # noinspection PyArgumentList
        if message != '':
            QMessageBox.warning(
                self.dialog, 'Information missing or invalid', message)
        else:
            self.dialog.accept()

    def populate_template(self, specification, template_name, output_name):
        """Populate the template based on user data.

        :param specification: Descriptive data that will be used to create
            the plugin.
        :type specification: PluginSpecification

        :param template_name: Name for the template.
        :type template_name: str

        :param output_name:  Name of the output file to create.
        :type output_name: str
        """
        template_file_path = os.path.join(str(self.plugin_builder_path),
                                          'plugin_template', template_name)
        output_name_path = os.path.join(self.plugin_path, output_name)

        if sys.platform == 'win32':
            # get short path name on windows
            template_file_path = win32api.GetShortPathName(template_file_path)
            # need to do it this way because GetShortPathName doesn't work
            # for non-existent directories
            output_name_path = os.path.join(
                win32api.GetShortPathName(self.plugin_path),
                output_name)

        template_file = open(template_file_path)
        content = template_file.read()
        template_file.close()
        template = Template(content)
        popped = template.substitute(specification.template_map)
        plugin_file = codecs.open(output_name_path, 'w', 'utf-8')
        plugin_file.write(popped)
        plugin_file.close()

    def show_help(self):
        """Display application help to the user."""
        help_file = 'file:///%s/help/index.html' % self.plugin_builder_path
        # For testing path:
        #QMessageBox.information(None, 'Help File', help_file)
        # noinspection PyCallByClass,PyTypeChecker
        QDesktopServices.openUrl(QUrl(help_file))
class PluginBuilder:
    """A QGIS plugin that allows you to build QGIS plugins."""

    def __init__(self, iface):
        """Constructor

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface

        """
        # Save reference to the QGIS interface
        self.iface = iface
        # noinspection PyArgumentList
        self.user_plugin_dir = QFileInfo(
            QgsApplication.qgisUserDbFilePath()).path() + 'python/plugins'
        self.plugin_builder_path = os.path.dirname(__file__)

        # class members
        self.action = None
        self.dialog = None
        self.plugin_path = None

    # noinspection PyPep8Naming
    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        # Create action that will start plugin configuration
        self.action = QAction(
            QIcon(':/plugins/plugin_builder/icon.png'),
            'Plugin Builder', self.iface.mainWindow())
        # connect the action to the run method
        self.action.triggered.connect(self.run)

        # Add toolbar button and menu item
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu('&Plugin Builder', self.action)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.iface.removePluginMenu('&Plugin Builder', self.action)
        self.iface.removeToolBarIcon(self.action)

    def _get_plugin_path(self):
        """Prompt the user for the path where the plugin should be written to.
        """
        while not QFileInfo(self.plugin_path).isWritable():
            # noinspection PyTypeChecker,PyArgumentList
            QMessageBox.critical(
                None, 'Error', 'Directory is not writeable')
            # noinspection PyCallByClass,PyTypeChecker
            self.plugin_path = QFileDialog.getExistingDirectory(
                self.dialog,
                'Select the Directory for your Plugin',
                self._last_used_path())
            if self.plugin_path == '':
                return False
        return True

    def _prepare_tests(self, specification):
        """Populate and write help files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        self.populate_template(
            specification, self.shared_dir,
            'help/source/conf.py.tmpl', 'help/source/conf.py')
        self.populate_template(
            specification, self.shared_dir,
            'help/source/index.rst.tmpl', 'help/source/index.rst')
        # copy the unit tests folder
        test_source = os.path.join(self.shared_dir, 'test')
        test_destination = os.path.join(self.plugin_path, 'test')
        copy(test_source, test_destination)

    def _prepare_scripts(self):
        """Copy the scripts folder."""
        scripts_source = os.path.join(self.shared_dir, 'scripts')
        copy(scripts_source, os.path.join(self.plugin_path, 'scripts'))

    def _prepare_i18n(self):
        """Copy the i18n folder."""
        scripts_source = os.path.join(self.shared_dir, 'i18n')
        copy(scripts_source, os.path.join(self.plugin_path, 'i18n'))

    def _prepare_help(self):
        """Prepare the help directory."""
        # Copy over pylintrc
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(self.shared_dir, 'pylintrc'),
            os.path.join(self.plugin_path, 'pylintrc'))
        # Create sphinx default project for help
        QDir().mkdir(self.plugin_path + '/help')
        QDir().mkdir(self.plugin_path + '/help/build')
        QDir().mkdir(self.plugin_path + '/help/source')
        QDir().mkdir(self.plugin_path + '/help/source/_static')
        QDir().mkdir(self.plugin_path + '/help/source/_templates')
        # copy doc makefiles
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(self.shared_dir, 'help/make.bat'),
            os.path.join(self.plugin_path, 'help/make.bat'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(self.shared_dir, 'help/Makefile'),
            os.path.join(self.plugin_path, 'help/Makefile'))

    def _prepare_code(self, specification):
        """Prepare the code turning templates into python.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        # process the user entries
        if specification.gen_makefile:
            self.populate_template(
                specification, self.shared_dir, 'Makefile.tmpl', 'Makefile')
        if specification.gen_pb_tool:
            self.populate_template(
                specification, self.shared_dir, 'pb_tool.tmpl', 'pb_tool.cfg')
        self.populate_template(
            specification, self.template_dir, '__init__.tmpl', '__init__.py')
        self.populate_template(
            specification, self.template_dir, 'module_name.tmpl',
            '%s.py' % specification.module_name)
        if specification.gen_scripts:
            release_script = QFile(os.path.join(self.shared_dir, 'release.sh'))
            release_script.copy(os.path.join(self.plugin_path, 'release.sh'))
            plugin_upload = QFile(
                os.path.join(self.shared_dir, 'plugin_upload.py'))
            plugin_upload.copy(
                os.path.join(self.plugin_path, 'plugin_upload.py'))
            # noinspection PyCallByClass,PyTypeChecker
            QFile.setPermissions(
                os.path.join(self.plugin_path, 'plugin_upload.py'),
                QFile.ReadOwner | QFile.WriteOwner | QFile.ExeOwner |
                QFile.ReadUser | QFile.WriteUser | QFile.ExeUser |
                QFile.ReadGroup | QFile.ExeGroup | QFile.ReadOther |
                QFile.ExeOther)

    def _prepare_specific_files(self, specification):
        """Prepare specific templates and files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        for template_name, output_name in \
                self.template.template_files(specification).iteritems():
            self.populate_template(
                specification, self.template_dir,
                template_name, output_name)

        # copy the non-generated files to the new plugin dir
        for template_file, output_name in \
                self.template.copy_files(specification).iteritems():
            file = QFile(os.path.join(self.template_dir, template_file))
            file.copy(os.path.join(self.plugin_path, output_name))

    def _prepare_readme(self, specification, template_module_name):
        """Prepare the README file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_module_name: Base name of the module for the new
            plugin.
        :type template_module_name: str
        """
        # populate the results readme text template
        template_file = open(os.path.join(
            self.template_dir,
            'readme.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        # TODO: update this to simply pass the specification.template_map
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        popped = template.substitute(result_map)
        # write the results info to the README txt file
        readme_txt = codecs.open(os.path.join(
            str(self.plugin_path), 'README.txt'), 'w', 'utf-8')
        readme_txt.write(popped)
        readme_txt.close()

    def _prepare_metadata(self, specification):
        """Prepare metadata file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        metadata_file = codecs.open(os.path.join(
            str(self.plugin_path), 'metadata.txt'), 'w', 'utf-8')
        metadata_comment = (
            '# This file contains metadata for your plugin. Since \n'
            '# version 2.0 of QGIS this is the proper way to supply \n'
            '# information about a plugin. The old method of \n'
            '# embedding metadata in __init__.py will \n'
            '# is no longer supported since version 2.0.\n\n'
            '# This file should be included when you package your plugin.'
            '# Mandatory items:\n\n')
        metadata_file.write(metadata_comment)
        metadata_file.write('[general]\n')
        metadata_file.write('name=%s\n' % specification.title)
        metadata_file.write(
            'qgisMinimumVersion=%s\n' % specification.qgis_minimum_version)
        metadata_file.write(
            'description=%s\n' % specification.description)
        metadata_file.write(
            'version=%s\n' % specification.plugin_version)
        metadata_file.write(
            'author=%s\n' % specification.author)
        metadata_file.write(
            'email=%s\n\n' % specification.email_address)
        metadata_file.write(
            'about=%s\n\n' % specification.about)
        metadata_file.write(
            'tracker=%s\n' % specification.tracker)
        metadata_file.write(
            'repository=%s\n' % specification.repository)
        metadata_file.write(
            '# End of mandatory metadata\n\n')
        metadata_file.write(
            '# Recommended items:\n\n')
        metadata_file.write(
            '# Uncomment the following line and add your changelog:\n')
        metadata_file.write(
            '# changelog=\n\n')
        metadata_file.write(
            '# Tags are comma separated with spaces allowed\n')
        metadata_file.write(
            'tags=%s\n\n' % specification.tags)
        metadata_file.write(
            'homepage=%s\n' % specification.homepage)
        metadata_file.write(
            'category=%s\n' % self.template.category)
        metadata_file.write(
            'icon=%s\n' % specification.icon)
        metadata_file.write(
            '# experimental flag\n')
        metadata_file.write(
            'experimental=%s\n\n' % specification.experimental)
        metadata_file.write(
            '# deprecated flag (applies to the whole plugin, not '
            'just a single version)\n')
        metadata_file.write('deprecated=%s\n\n' % specification.deprecated)
        metadata_file.close()

    def _prepare_results_html(self, specification):
        """Prepare results README.html file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        template_module_name = \
            specification.template_map['TemplateModuleName']
        template_file = open(os.path.join(
            self.template_dir, 'results.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        results_popped = template.substitute(result_map)
        # write the results info to the README HTML file
        readme = codecs.open(os.path.join(
            str(self.plugin_path), 'README.html'), 'w', 'utf-8')
        readme.write(results_popped)
        readme.close()
        return results_popped, template_module_name

    def _create_plugin_directory(self):
        """Create the plugin directory using the class name."""
        # remove spaces from the plugin name
        self.plugin_path = os.path.join(
            str(self.plugin_path),
            str(self.dialog.class_name.text()))
        QDir().mkdir(self.plugin_path)

    def _last_used_path(self):
        return QSettings().value('PluginBuilder/last_path', '.')

    def _set_last_used_path(self, value):
        QSettings().setValue('PluginBuilder/last_path', value)

    def _select_tags(self):
        tag_dialog = SelectTagsDialog()
        # if the user has their own taglist, use it
        user_tag_list = os.path.join(os.path.expanduser("~"), '.plugin_tags.txt')
        if os.path.exists(user_tag_list):
            tag_file = user_tag_list
        else:
            tag_file = os.path.join(str(self.plugin_builder_path),
                                          'taglist.txt')

        with open(tag_file) as tf:
            tags = tf.readlines()

        model = QStandardItemModel()

        for tag in tags:
            item = QStandardItem(tag[:-1])
            model.appendRow(item)

        tag_dialog.listView.setModel(model)
        tag_dialog.show()
        ok = tag_dialog.exec_()
        if ok:
            selected = tag_dialog.listView.selectedIndexes()
            seltags = []
            for tag in selected:
                seltags.append(tag.data())
            taglist = ", ".join(seltags)
            self.dialog.tags.setText(taglist)
        #QMessageBox.information(None, "Selection", seltags)

    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.helpRequested.connect(self.show_help)
        self.dialog.select_tags.clicked.connect(self._select_tags)

        # show the dialog
        self.dialog.show()
        self.dialog.adjustSize()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', self._last_used_path())
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        self._set_last_used_path(self.plugin_path)
        self._create_plugin_directory()
        self.template = self.dialog.template()
        self.template_dir = os.path.join(
            self.template.subdir(), 'template')
        self.shared_dir = os.path.join(
            str(self.plugin_builder_path), 'plugin_templates', 'shared')

        template_map = self.template.template_map(specification, self.dialog)
        specification.template_map.update(template_map)

        self._prepare_code(specification)
        if specification.gen_help:
            self._prepare_help()
        if specification.gen_tests:
            self._prepare_tests(specification)

        if specification.gen_scripts:
            self._prepare_scripts()

        if specification.gen_i18n:
            self._prepare_i18n()

        #resource = QFile(os.path.join(self.template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        self._prepare_specific_files(specification)

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.textBrowser.setOpenExternalLinks(True)
        results_dialog.textBrowser.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()

    def populate_template(self, specification, template_dir,
                          template_name, output_name):
        """Populate the template based on user data.

        :param specification: Descriptive data that will be used to create
            the plugin.
        :type specification: PluginSpecification

        :param template_dir: Directory where template is.
        :type template_dir: str

        :param template_name: Name for the template.
        :type template_name: str

        :param output_name:  Name of the output file to create.
        :type output_name: str
        """
        template_file_path = os.path.join(template_dir, template_name)
        output_name_path = os.path.join(self.plugin_path, output_name)

        template_file = open(template_file_path)
        content = template_file.read()
        template_file.close()
        template = Template(content)
        popped = template.substitute(specification.template_map)
        plugin_file = codecs.open(output_name_path, 'w', 'utf-8')
        plugin_file.write(popped)
        plugin_file.close()

    def show_help(self):
        """Display application help to the user."""
        help_file = 'file:///%s/help/index.html' % self.plugin_builder_path
        # For testing path:
        #QMessageBox.information(None, 'Help File', help_file)
        # noinspection PyCallByClass,PyTypeChecker
        QDesktopServices.openUrl(QUrl(help_file))
    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # get version
        cfg = ConfigParser.ConfigParser()
        cfg.read(os.path.join(self.plugin_builder_path, 'metadata.txt'))
        version = cfg.get('general', 'version')
        self.dialog.setWindowTitle('QGIS Plugin Builder - {}'.format(version))

        # connect the ok button to our method
        self.dialog.button_box.helpRequested.connect(self.show_help)
        self.dialog.select_tags.clicked.connect(self._select_tags)

        # show the dialog
        self.dialog.show()
        self.dialog.adjustSize()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', self._last_used_path())
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        self._set_last_used_path(self.plugin_path)
        self._create_plugin_directory()
        self.template = self.dialog.template()
        self.template_dir = os.path.join(
            self.template.subdir(), 'template')
        self.shared_dir = os.path.join(
            str(self.plugin_builder_path), 'plugin_templates', 'shared')

        template_map = self.template.template_map(specification, self.dialog)
        specification.template_map.update(template_map)

        self._prepare_code(specification)
        if specification.gen_help:
            self._prepare_help()
        if specification.gen_tests:
            self._prepare_tests(specification)

        if specification.gen_scripts:
            self._prepare_scripts()

        if specification.gen_i18n:
            self._prepare_i18n()

        #resource = QFile(os.path.join(self.template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        self._prepare_specific_files(specification)

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.textBrowser.setOpenExternalLinks(True)
        results_dialog.textBrowser.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()
class PluginBuilder:
    """A QGIS plugin that allows you to build QGIS plugins."""

    def __init__(self, iface):
        """Constructor

        :param iface: An interface instance that will be passed to this class
            which provides the hook by which you can manipulate the QGIS
            application at run time.
        :type iface: QgsInterface

        """
        # Save reference to the QGIS interface
        self.iface = iface
        # noinspection PyArgumentList
        self.user_plugin_dir = QFileInfo(
            QgsApplication.qgisUserDbFilePath()).path() + 'python/plugins'
        self.plugin_builder_path = os.path.dirname(__file__)

        # class members
        self.action = None
        self.dialog = None
        self.plugin_path = None

    # noinspection PyPep8Naming
    def initGui(self):
        """Create the menu entries and toolbar icons inside the QGIS GUI."""
        # Create action that will start plugin configuration
        self.action = QAction(
            QIcon(':/plugins/plugin_builder/icon.png'),
            'Plugin Builder', self.iface.mainWindow())
        # connect the action to the run method
        self.action.triggered.connect(self.run)

        # Add toolbar button and menu item
        self.iface.addToolBarIcon(self.action)
        self.iface.addPluginToMenu('&Plugin Builder', self.action)

    def unload(self):
        """Removes the plugin menu item and icon from QGIS GUI."""
        self.iface.removePluginMenu('&Plugin Builder', self.action)
        self.iface.removeToolBarIcon(self.action)

    def _get_plugin_path(self):
        """Prompt the user for the path where the plugin should be written to.
        """
        while not QFileInfo(self.plugin_path).isWritable():
            # noinspection PyTypeChecker,PyArgumentList
            QMessageBox.critical(
                None, 'Error', 'Directory is not writeable')
            # noinspection PyCallByClass,PyTypeChecker
            self.plugin_path = QFileDialog.getExistingDirectory(
                self.dialog,
                'Select the Directory for your Plugin',
                '.')
            if self.plugin_path == '':
                return False
        return True

    def _prepare_tests(self, specification):
        """Populate and write help files.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        self.populate_template(
            specification,
            'help/source/conf.py.tmpl', 'help/source/conf.py')
        self.populate_template(
            specification,
            'help/source/index.rst.tmpl', 'help/source/index.rst')
        # copy the unit tests folder
        test_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'test')
        test_destination = os.path.join(self.plugin_path, 'test')
        copy(test_source, test_destination)
        # These are templates so we don't copy it directly.
        os.remove(os.path.join(
            test_destination, 'test_module_name_dialog.templ'))
        os.remove(os.path.join(
            test_destination, 'test_resources.templ'))
        self.populate_template(
            specification,
            'test/test_module_name_dialog.templ',
            'test/test_%s_dialog.py' % specification.module_name)
        self.populate_template(
            specification,
            'test/test_resources.templ',
            'test/test_resources.py')

    def _prepare_scripts(self):
        """Copy the scripts folder."""
        scripts_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'scripts')
        copy(scripts_source, os.path.join(self.plugin_path, 'scripts'))
        # copy the i18n folder
        scripts_source = os.path.join(
            os.path.dirname(__file__), 'plugin_template', 'i18n')
        copy(scripts_source, os.path.join(self.plugin_path, 'i18n'))

    def _prepare_help(self, template_dir):
        """Prepare the help directory.

        :param template_dir: Directory where template is.
        :type template_dir: str
        """
        # Copy over pylintrc
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'pylintrc'),
            os.path.join(self.plugin_path, 'pylintrc'))
        # Create sphinx default project for help
        QDir().mkdir(self.plugin_path + '/help')
        QDir().mkdir(self.plugin_path + '/help/build')
        QDir().mkdir(self.plugin_path + '/help/source')
        QDir().mkdir(self.plugin_path + '/help/source/_static')
        QDir().mkdir(self.plugin_path + '/help/source/_templates')
        # copy doc makefiles
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'help/make.bat'),
            os.path.join(self.plugin_path, 'help/make.bat'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.copy(
            os.path.join(template_dir, 'help/Makefile'),
            os.path.join(self.plugin_path, 'help/Makefile'))

    def _prepare_code(self, specification, template_dir):
        """Prepare the code turning templates into python.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_dir: Directory where template is.
        :type template_dir: str
        """
        # process the user entries
        self.populate_template(
            specification, 'Makefile.tmpl', 'Makefile')
        self.populate_template(
            specification, '__init__.tmpl', '__init__.py')
        self.populate_template(
            specification, 'module_name.tmpl',
            '%s.py' % specification.module_name)
        self.populate_template(
            specification, 'module_name_dialog.tmpl',
            '%s_dialog.py' % specification.module_name)
        self.populate_template(
            specification, 'module_name_dialog_base.ui.tmpl',
            '%s_dialog_base.ui' % specification.module_name)
        self.populate_template(
            specification, 'resources.tmpl', 'resources.qrc')
        # copy the non-generated files to the new plugin dir
        icon = QFile(os.path.join(template_dir, 'icon.png'))
        icon.copy(os.path.join(self.plugin_path, 'icon.png'))
        release_script = QFile(os.path.join(template_dir, 'release.sh'))
        release_script.copy(os.path.join(self.plugin_path, 'release.sh'))
        plugin_upload = QFile(
            os.path.join(template_dir, 'plugin_upload.py'))
        plugin_upload.copy(
            os.path.join(self.plugin_path, 'plugin_upload.py'))
        # noinspection PyCallByClass,PyTypeChecker
        QFile.setPermissions(
            os.path.join(self.plugin_path, 'plugin_upload.py'),
            QFile.ReadOwner | QFile.WriteOwner | QFile.ExeOwner |
            QFile.ReadUser | QFile.WriteUser | QFile.ExeUser |
            QFile.ReadGroup | QFile.ExeGroup | QFile.ReadOther |
            QFile.ExeOther)

    def _prepare_readme(self, specification, template_module_name):
        """Prepare the README file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification

        :param template_module_name: Base name of the module for the new
            plugin.
        :type template_module_name: str
        """
        # populate the results readme text template
        template_file = open(os.path.join(
            str(self.plugin_builder_path),
            'plugin_template',
            'readme.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        # TODO: update this to simply pass the specification.template_map
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        popped = template.substitute(result_map)
        # write the results info to the README txt file
        readme_txt = codecs.open(os.path.join(
            str(self.plugin_path), 'README.txt'), 'w', 'utf-8')
        readme_txt.write(popped)
        readme_txt.close()

    def _prepare_metadata(self, specification):
        """Prepare metadata file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        metadata_file = codecs.open(os.path.join(
            str(self.plugin_path), 'metadata.txt'), 'w', 'utf-8')
        metadata_comment = (
            '# This file contains metadata for your plugin. Since \n'
            '# version 2.0 of QGIS this is the proper way to supply \n'
            '# information about a plugin. The old method of \n'
            '# embedding metadata in __init__.py will \n'
            '# is no longer supported since version 2.0.\n\n'
            '# This file should be included when you package your plugin.'
            '# Mandatory items:\n\n')
        metadata_file.write(metadata_comment)
        metadata_file.write('[general]\n')
        metadata_file.write('name=%s\n' % specification.title)
        metadata_file.write(
            'qgisMinimumVersion=%s\n' % specification.qgis_minimum_version)
        metadata_file.write(
            'description=%s\n' % specification.description)
        metadata_file.write(
            'version=%s\n' % specification.plugin_version)
        metadata_file.write(
            'author=%s\n' % specification.author)
        metadata_file.write(
            'email=%s\n\n' % specification.email_address)
        metadata_file.write(
            '# End of mandatory metadata\n\n')
        metadata_file.write(
            '# Optional items:\n\n')
        metadata_file.write(
            '# Uncomment the following line and add your changelog:\n')
        metadata_file.write(
            '# changelog=\n\n')
        metadata_file.write(
            '# Tags are comma separated with spaces allowed\n')
        metadata_file.write(
            'tags=%s\n\n' % specification.tags)
        metadata_file.write(
            'homepage=%s\n' % specification.homepage)
        metadata_file.write(
            'tracker=%s\n' % specification.tracker)
        metadata_file.write(
            'repository=%s\n' % specification.repository)
        metadata_file.write(
            'icon=%s\n' % specification.icon)
        metadata_file.write(
            '# experimental flag\n')
        metadata_file.write(
            'experimental=%s\n\n' % specification.experimental)
        metadata_file.write(
            '# deprecated flag (applies to the whole plugin, not '
            'just a single version)\n')
        metadata_file.write('deprecated=%s\n\n' % specification.deprecated)
        metadata_file.close()

    def _prepare_results_html(self, specification):
        """Prepare results README.html file.

        :param specification: Specification instance containing template
            replacement keys/values.
        :type specification: PluginSpecification
        """
        template_module_name = \
            specification.template_map['TemplateModuleName']
        template_file = open(os.path.join(
            str(self.plugin_builder_path),
            'plugin_template',
            'results.tmpl'))
        content = template_file.read()
        template_file.close()
        template = Template(content)
        result_map = {
            'PluginDir': self.plugin_path,
            'TemplateClass': specification.template_map['TemplateClass'],
            'TemplateModuleName': template_module_name,
            'UserPluginDir': self.user_plugin_dir,
            'TemplateVCSFormat': specification.template_map[
                'TemplateVCSFormat']}
        results_popped = template.substitute(result_map)
        # write the results info to the README HTML file
        readme = codecs.open(os.path.join(
            str(self.plugin_path), 'README.html'), 'w', 'utf-8')
        readme.write(results_popped)
        readme.close()
        return results_popped, template_module_name

    def _create_plugin_directory(self):
        """Create the plugin directory using the class name."""
        # remove spaces from the plugin name
        self.plugin_path = os.path.join(
            str(self.plugin_path),
            str(self.dialog.class_name.text()))
        QDir().mkdir(self.plugin_path)
        template_dir = os.path.join(
            str(self.plugin_builder_path), 'plugin_template')
        return template_dir

    def run(self):
        """Run method that performs all the real work"""
        # create and show the dialog
        self.dialog = PluginBuilderDialog()

        # connect the ok button to our method
        self.dialog.button_box.accepted.connect(self.validate_entries)
        self.dialog.button_box.helpRequested.connect(self.show_help)

        # show the dialog
        self.dialog.show()
        result = self.dialog.exec_()
        if result == QFileDialog.Rejected:
            return

        specification = PluginSpecification(self.dialog)
        # get the location for the plugin
        # noinspection PyCallByClass,PyTypeChecker
        self.plugin_path = QFileDialog.getExistingDirectory(
            self.dialog, 'Select the Directory for your Plugin', '.')
        if self.plugin_path == '':
            return
        else:
            if not self._get_plugin_path():
                return False

        template_dir = self._create_plugin_directory()
        self._prepare_code(specification, template_dir)
        self._prepare_help(template_dir)
        self._prepare_tests(specification)

        self._prepare_scripts()

        #resource = QFile(os.path.join(template_dir, 'resources.qrc'))
        #resource.copy(os.path.join(self.plugin_path, 'resources.qrc'))

        results_popped, template_module_name = self._prepare_results_html(
            specification)

        self._prepare_readme(specification, template_module_name)
        self._prepare_metadata(specification)
        # show the results
        results_dialog = ResultDialog()
        results_dialog.web_view.setHtml(results_popped)
        results_dialog.show()
        results_dialog.exec_()

    def validate_entries(self):
        """Check to see that all fields have been entered."""
        message = ''
        dialog = self.dialog
        if dialog.class_name.text() == '' or \
           dialog.title.text() == '' or \
           dialog.description.text() == '' or \
           dialog.module_name.text() == '' or \
           dialog.plugin_version.text() == '' or \
           dialog.qgis_minimum_version.text() == '' or \
           dialog.menu_text.text() == '' or \
           dialog.author.text() == '' or \
           dialog.email_address.text() == '':
                message = (
                    'Some required fields are missing. '
                    'Please complete the form.\n')
        try:
            # Assigning to _ is python sugar for a variable that will be unused
            _ = float(str(dialog.plugin_version.text()))
            _ = float(str(dialog.qgis_minimum_version.text()))
        except ValueError:
            message += 'Version numbers must be numeric.\n'
        # validate plugin name
        # check that we have only ascii char in class name
        try:
            unicode(dialog.class_name.text()).decode('ascii')
        except UnicodeEncodeError:
            dialog.class_name.setText(
                unicode(
                    dialog.class_name.text()).encode('ascii', 'ignore'))
            message += (
                'The Class name must be ASCII characters only, '
                'the name has been modified for you. \n')
        # check space and force CamelCase
        if str(dialog.class_name.text()).find(' ') > -1:
            class_name = capwords(str(dialog.class_name.text()))
            dialog.class_name.setText(class_name.replace(' ', ''))
            message += (
                'The Class name must use CamelCase. '
                'No spaces are allowed; the name has been modified for you.')
        # noinspection PyArgumentList
        if message != '':
            QMessageBox.warning(
                self.dialog, 'Information missing or invalid', message)
        else:
            self.dialog.accept()

    def populate_template(self, specification, template_name, output_name):
        """Populate the template based on user data.

        :param specification: Descriptive data that will be used to create
            the plugin.
        :type specification: PluginSpecification

        :param template_name: Name for the template.
        :type template_name: str

        :param output_name:  Name of the output file to create.
        :type output_name: str
        """
        template_file_path = os.path.join(str(self.plugin_builder_path),
                                          'plugin_template', template_name)
        output_name_path = os.path.join(self.plugin_path, output_name)

        if sys.platform == 'win32':
            # get short path name on windows
            template_file_path = win32api.GetShortPathName(template_file_path)
            output_name_path = win32api.GetShortPathName(output_name_path)

        template_file = open(template_file_path)
        content = template_file.read()
        template_file.close()
        template = Template(content)
        popped = template.substitute(specification.template_map)
        plugin_file = codecs.open(output_name_path, 'w', 'utf-8')
        plugin_file.write(popped)
        plugin_file.close()

    def show_help(self):
        """Display application help to the user."""
        help_file = 'file:///%s/help/index.html' % self.plugin_builder_path
        # For testing path:
        #QMessageBox.information(None, 'Help File', help_file)
        # noinspection PyCallByClass,PyTypeChecker
        QDesktopServices.openUrl(QUrl(help_file))