We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
inability to uninstall plugins
Attempt to uninstall plugin gives:

```
nikola plugin -r localsearch
[2016-06-03T09:30:46Z] ERROR: plugin: Unknown plugin: localsearch
```

However, plugin is installed:

```
tree plugins/
plugins/
├── __init__.py
└── localsearch
    ├── conf.py.sample
    ├── localsearch
    │   ├── files
    │   │   ├── assets
    │   │   │   ├── css
    │   │   │   │   ├── img
    │   │   │   │   │   ├── loader.gif
    │   │   │   │   │   └── search.png
    │   │   │   │   └── tipuesearch.css
    │   │   │   └── js
    │   │   │       ├── tipuesearch.js
    │   │   │       ├── tipuesearch.min.js
    │   │   │       └── tipuesearch_set.js
    │   │   └── tipue_search.html
    │   ├── __init__.py
    │   └── __pycache__
    │       └── __init__.cpython-35.pyc
    ├── localsearch.plugin
    ├── MIT-LICENSE.txt
    └── README.md
```

Version of Nikola is 7.7.8.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nikola/plugins/command/plugin.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 
3 # Copyright © 2012-2016 Roberto Alsina and others.
4 
5 # Permission is hereby granted, free of charge, to any
6 # person obtaining a copy of this software and associated
7 # documentation files (the "Software"), to deal in the
8 # Software without restriction, including without limitation
9 # the rights to use, copy, modify, merge, publish,
10 # distribute, sublicense, and/or sell copies of the
11 # Software, and to permit persons to whom the Software is
12 # furnished to do so, subject to the following conditions:
13 #
14 # The above copyright notice and this permission notice
15 # shall be included in all copies or substantial portions of
16 # the Software.
17 #
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
21 # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
22 # OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23 # OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
24 # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
25 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 
27 """Manage plugins."""
28 
29 from __future__ import print_function
30 import io
31 import os
32 import sys
33 import shutil
34 import subprocess
35 import time
36 import requests
37 
38 import pygments
39 from pygments.lexers import PythonLexer
40 from pygments.formatters import TerminalFormatter
41 
42 from nikola.plugin_categories import Command
43 from nikola import utils
44 
45 LOGGER = utils.get_logger('plugin', utils.STDERR_HANDLER)
46 
47 
48 class CommandPlugin(Command):
49     """Manage plugins."""
50 
51     json = None
52     name = "plugin"
53     doc_usage = "[[-u][--user] --install name] | [[-u] [-l |--upgrade|--list-installed] | [--uninstall name]]"
54     doc_purpose = "manage plugins"
55     output_dir = None
56     needs_config = False
57     cmd_options = [
58         {
59             'name': 'install',
60             'short': 'i',
61             'long': 'install',
62             'type': str,
63             'default': '',
64             'help': 'Install a plugin.',
65         },
66         {
67             'name': 'uninstall',
68             'long': 'uninstall',
69             'short': 'r',
70             'type': str,
71             'default': '',
72             'help': 'Uninstall a plugin.'
73         },
74         {
75             'name': 'list',
76             'short': 'l',
77             'long': 'list',
78             'type': bool,
79             'default': False,
80             'help': 'Show list of available plugins.'
81         },
82         {
83             'name': 'url',
84             'short': 'u',
85             'long': 'url',
86             'type': str,
87             'help': "URL for the plugin repository (default: "
88                     "https://plugins.getnikola.com/v7/plugins.json)",
89             'default': 'https://plugins.getnikola.com/v7/plugins.json'
90         },
91         {
92             'name': 'user',
93             'long': 'user',
94             'type': bool,
95             'help': "Install user-wide, available for all sites.",
96             'default': False
97         },
98         {
99             'name': 'upgrade',
100             'long': 'upgrade',
101             'type': bool,
102             'help': "Upgrade all installed plugins.",
103             'default': False
104         },
105         {
106             'name': 'list_installed',
107             'long': 'list-installed',
108             'type': bool,
109             'help': "List the installed plugins with their location.",
110             'default': False
111         },
112     ]
113 
114     def _execute(self, options, args):
115         """Install plugin into current site."""
116         url = options['url']
117         user_mode = options['user']
118 
119         # See the "mode" we need to operate in
120         install = options.get('install')
121         uninstall = options.get('uninstall')
122         upgrade = options.get('upgrade')
123         list_available = options.get('list')
124         list_installed = options.get('list_installed')
125         show_install_notes = options.get('show_install_notes', True)
126         command_count = [bool(x) for x in (
127             install,
128             uninstall,
129             upgrade,
130             list_available,
131             list_installed)].count(True)
132         if command_count > 1 or command_count == 0:
133             print(self.help())
134             return 2
135 
136         if options.get('output_dir') is not None:
137             self.output_dir = options.get('output_dir')
138         else:
139             if not self.site.configured and not user_mode and install:
140                 LOGGER.notice('No site found, assuming --user')
141                 user_mode = True
142 
143             if user_mode:
144                 self.output_dir = os.path.expanduser('~/.nikola/plugins')
145             else:
146                 self.output_dir = 'plugins'
147 
148         if list_available:
149             return self.list_available(url)
150         elif list_installed:
151             return self.list_installed()
152         elif upgrade:
153             return self.do_upgrade(url)
154         elif uninstall:
155             return self.do_uninstall(uninstall)
156         elif install:
157             return self.do_install(url, install, show_install_notes)
158 
159     def list_available(self, url):
160         """List all available plugins."""
161         data = self.get_json(url)
162         print("Available Plugins:")
163         print("------------------")
164         for plugin in sorted(data.keys()):
165             print(plugin)
166         return 0
167 
168     def list_installed(self):
169         """List installed plugins."""
170         plugins = []
171         for plugin in self.site.plugin_manager.getAllPlugins():
172             p = plugin.path
173             if os.path.isdir(p):
174                 p = p + os.sep
175             else:
176                 p = p + '.py'
177             plugins.append([plugin.name, p])
178 
179         plugins.sort()
180         print('Installed Plugins')
181         print('-----------------')
182         for name, path in plugins:
183             print('{0} at {1}'.format(name, path))
184         print('\n\nAlso, you have disabled these plugins: {}'.format(self.site.config['DISABLED_PLUGINS']))
185         return 0
186 
187     def do_upgrade(self, url):
188         """Upgrade all installed plugins."""
189         LOGGER.warning('This is not very smart, it just reinstalls some plugins and hopes for the best')
190         data = self.get_json(url)
191         plugins = []
192         for plugin in self.site.plugin_manager.getAllPlugins():
193             p = plugin.path
194             if os.path.isdir(p):
195                 p = p + os.sep
196             else:
197                 p = p + '.py'
198             if plugin.name in data:
199                 plugins.append([plugin.name, p])
200         print('Will upgrade {0} plugins: {1}'.format(len(plugins), ', '.join(n for n, _ in plugins)))
201         for name, path in plugins:
202             print('Upgrading {0}'.format(name))
203             p = path
204             while True:
205                 tail, head = os.path.split(path)
206                 if head == 'plugins':
207                     self.output_dir = path
208                     break
209                 elif tail == '':
210                     LOGGER.error("Can't find the plugins folder for path: {0}".format(p))
211                     return 1
212                 else:
213                     path = tail
214             self.do_install(url, name)
215         return 0
216 
217     def do_install(self, url, name, show_install_notes=True):
218         """Download and install a plugin."""
219         data = self.get_json(url)
220         if name in data:
221             utils.makedirs(self.output_dir)
222             url = data[name]
223             LOGGER.info("Downloading '{0}'".format(url))
224             try:
225                 zip_data = requests.get(url).content
226             except requests.exceptions.SSLError:
227                 LOGGER.warning("SSL error, using http instead of https (press ^C to abort)")
228                 time.sleep(1)
229                 url = url.replace('https', 'http', 1)
230                 zip_data = requests.get(url).content
231 
232             zip_file = io.BytesIO()
233             zip_file.write(zip_data)
234             LOGGER.info('Extracting: {0} into {1}/'.format(name, self.output_dir))
235             utils.extract_all(zip_file, self.output_dir)
236             dest_path = os.path.join(self.output_dir, name)
237         else:
238             try:
239                 plugin_path = utils.get_plugin_path(name)
240             except:
241                 LOGGER.error("Can't find plugin " + name)
242                 return 1
243 
244             utils.makedirs(self.output_dir)
245             dest_path = os.path.join(self.output_dir, name)
246             if os.path.exists(dest_path):
247                 LOGGER.error("{0} is already installed".format(name))
248                 return 1
249 
250             LOGGER.info('Copying {0} into plugins'.format(plugin_path))
251             shutil.copytree(plugin_path, dest_path)
252 
253         reqpath = os.path.join(dest_path, 'requirements.txt')
254         if os.path.exists(reqpath):
255             LOGGER.notice('This plugin has Python dependencies.')
256             LOGGER.info('Installing dependencies with pip...')
257             try:
258                 subprocess.check_call((sys.executable, '-m', 'pip', 'install', '-r', reqpath))
259             except subprocess.CalledProcessError:
260                 LOGGER.error('Could not install the dependencies.')
261                 print('Contents of the requirements.txt file:\n')
262                 with io.open(reqpath, 'r', encoding='utf-8') as fh:
263                     print(utils.indent(fh.read(), 4 * ' '))
264                 print('You have to install those yourself or through a '
265                       'package manager.')
266             else:
267                 LOGGER.info('Dependency installation succeeded.')
268         reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')
269         if os.path.exists(reqnpypath):
270             LOGGER.notice('This plugin has third-party '
271                           'dependencies you need to install '
272                           'manually.')
273             print('Contents of the requirements-nonpy.txt file:\n')
274             with io.open(reqnpypath, 'r', encoding='utf-8') as fh:
275                 for l in fh.readlines():
276                     i, j = l.split('::')
277                     print(utils.indent(i.strip(), 4 * ' '))
278                     print(utils.indent(j.strip(), 8 * ' '))
279                     print()
280 
281             print('You have to install those yourself or through a package '
282                   'manager.')
283         confpypath = os.path.join(dest_path, 'conf.py.sample')
284         if os.path.exists(confpypath) and show_install_notes:
285             LOGGER.notice('This plugin has a sample config file.  Integrate it with yours in order to make this plugin work!')
286             print('Contents of the conf.py.sample file:\n')
287             with io.open(confpypath, 'r', encoding='utf-8') as fh:
288                 if self.site.colorful:
289                     print(utils.indent(pygments.highlight(
290                         fh.read(), PythonLexer(), TerminalFormatter()),
291                         4 * ' '))
292                 else:
293                     print(utils.indent(fh.read(), 4 * ' '))
294         return 0
295 
296     def do_uninstall(self, name):
297         """Uninstall a plugin."""
298         for plugin in self.site.plugin_manager.getAllPlugins():  # FIXME: this is repeated thrice
299             p = plugin.path
300             if os.path.isdir(p):
301                 p = p + os.sep
302             else:
303                 p = os.path.dirname(p)
304             if name == plugin.name:  # Uninstall this one
305                 LOGGER.warning('About to uninstall plugin: {0}'.format(name))
306                 LOGGER.warning('This will delete {0}'.format(p))
307                 sure = utils.ask_yesno('Are you sure?')
308                 if sure:
309                     LOGGER.warning('Removing {0}'.format(p))
310                     shutil.rmtree(p)
311                     return 0
312                 return 1
313         LOGGER.error('Unknown plugin: {0}'.format(name))
314         return 1
315 
316     def get_json(self, url):
317         """Download the JSON file with all plugins."""
318         if self.json is None:
319             try:
320                 self.json = requests.get(url).json()
321             except requests.exceptions.SSLError:
322                 LOGGER.warning("SSL error, using http instead of https (press ^C to abort)")
323                 time.sleep(1)
324                 url = url.replace('https', 'http', 1)
325                 self.json = requests.get(url).json()
326         return self.json
327
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py
--- a/nikola/plugins/command/plugin.py
+++ b/nikola/plugins/command/plugin.py
@@ -296,12 +296,15 @@
     def do_uninstall(self, name):
         """Uninstall a plugin."""
         for plugin in self.site.plugin_manager.getAllPlugins():  # FIXME: this is repeated thrice
-            p = plugin.path
-            if os.path.isdir(p):
-                p = p + os.sep
-            else:
-                p = os.path.dirname(p)
             if name == plugin.name:  # Uninstall this one
+                p = plugin.path
+                if os.path.isdir(p):
+                    # Plugins that have a package in them need to delete parent
+                    # Issue #2356
+                    p = p + os.sep
+                    p = os.path.abspath(os.path.join(p, os.pardir))
+                else:
+                    p = os.path.dirname(p)
                 LOGGER.warning('About to uninstall plugin: {0}'.format(name))
                 LOGGER.warning('This will delete {0}'.format(p))
                 sure = utils.ask_yesno('Are you sure?')

{"golden_diff": "diff --git a/nikola/plugins/command/plugin.py b/nikola/plugins/command/plugin.py\n--- a/nikola/plugins/command/plugin.py\n+++ b/nikola/plugins/command/plugin.py\n@@ -296,12 +296,15 @@\n     def do_uninstall(self, name):\n         \"\"\"Uninstall a plugin.\"\"\"\n         for plugin in self.site.plugin_manager.getAllPlugins():  # FIXME: this is repeated thrice\n-            p = plugin.path\n-            if os.path.isdir(p):\n-                p = p + os.sep\n-            else:\n-                p = os.path.dirname(p)\n             if name == plugin.name:  # Uninstall this one\n+                p = plugin.path\n+                if os.path.isdir(p):\n+                    # Plugins that have a package in them need to delete parent\n+                    # Issue #2356\n+                    p = p + os.sep\n+                    p = os.path.abspath(os.path.join(p, os.pardir))\n+                else:\n+                    p = os.path.dirname(p)\n                 LOGGER.warning('About to uninstall plugin: {0}'.format(name))\n                 LOGGER.warning('This will delete {0}'.format(p))\n                 sure = utils.ask_yesno('Are you sure?')\n", "issue": "inability to uninstall plugins\nAttempt to uninstall plugin gives:\n\n```\nnikola plugin -r localsearch\n[2016-06-03T09:30:46Z] ERROR: plugin: Unknown plugin: localsearch\n```\n\nHowever, plugin is installed:\n\n```\ntree plugins/\nplugins/\n\u251c\u2500\u2500 __init__.py\n\u2514\u2500\u2500 localsearch\n    \u251c\u2500\u2500 conf.py.sample\n    \u251c\u2500\u2500 localsearch\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 files\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 assets\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 css\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 img\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u251c\u2500\u2500 loader.gif\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 search.png\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 tipuesearch.css\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 js\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0     \u251c\u2500\u2500 tipuesearch.js\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0     \u251c\u2500\u2500 tipuesearch.min.js\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2502\u00a0\u00a0     \u2514\u2500\u2500 tipuesearch_set.js\n    \u2502\u00a0\u00a0 \u2502\u00a0\u00a0 \u2514\u2500\u2500 tipue_search.html\n    \u2502\u00a0\u00a0 \u251c\u2500\u2500 __init__.py\n    \u2502\u00a0\u00a0 \u2514\u2500\u2500 __pycache__\n    \u2502\u00a0\u00a0     \u2514\u2500\u2500 __init__.cpython-35.pyc\n    \u251c\u2500\u2500 localsearch.plugin\n    \u251c\u2500\u2500 MIT-LICENSE.txt\n    \u2514\u2500\u2500 README.md\n```\n\nVersion of Nikola is 7.7.8.\n\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright \u00a9 2012-2016 Roberto Alsina and others.\n\n# Permission is hereby granted, free of charge, to any\n# person obtaining a copy of this software and associated\n# documentation files (the \"Software\"), to deal in the\n# Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the\n# Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice\n# shall be included in all copies or substantial portions of\n# the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\n# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"Manage plugins.\"\"\"\n\nfrom __future__ import print_function\nimport io\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport time\nimport requests\n\nimport pygments\nfrom pygments.lexers import PythonLexer\nfrom pygments.formatters import TerminalFormatter\n\nfrom nikola.plugin_categories import Command\nfrom nikola import utils\n\nLOGGER = utils.get_logger('plugin', utils.STDERR_HANDLER)\n\n\nclass CommandPlugin(Command):\n    \"\"\"Manage plugins.\"\"\"\n\n    json = None\n    name = \"plugin\"\n    doc_usage = \"[[-u][--user] --install name] | [[-u] [-l |--upgrade|--list-installed] | [--uninstall name]]\"\n    doc_purpose = \"manage plugins\"\n    output_dir = None\n    needs_config = False\n    cmd_options = [\n        {\n            'name': 'install',\n            'short': 'i',\n            'long': 'install',\n            'type': str,\n            'default': '',\n            'help': 'Install a plugin.',\n        },\n        {\n            'name': 'uninstall',\n            'long': 'uninstall',\n            'short': 'r',\n            'type': str,\n            'default': '',\n            'help': 'Uninstall a plugin.'\n        },\n        {\n            'name': 'list',\n            'short': 'l',\n            'long': 'list',\n            'type': bool,\n            'default': False,\n            'help': 'Show list of available plugins.'\n        },\n        {\n            'name': 'url',\n            'short': 'u',\n            'long': 'url',\n            'type': str,\n            'help': \"URL for the plugin repository (default: \"\n                    \"https://plugins.getnikola.com/v7/plugins.json)\",\n            'default': 'https://plugins.getnikola.com/v7/plugins.json'\n        },\n        {\n            'name': 'user',\n            'long': 'user',\n            'type': bool,\n            'help': \"Install user-wide, available for all sites.\",\n            'default': False\n        },\n        {\n            'name': 'upgrade',\n            'long': 'upgrade',\n            'type': bool,\n            'help': \"Upgrade all installed plugins.\",\n            'default': False\n        },\n        {\n            'name': 'list_installed',\n            'long': 'list-installed',\n            'type': bool,\n            'help': \"List the installed plugins with their location.\",\n            'default': False\n        },\n    ]\n\n    def _execute(self, options, args):\n        \"\"\"Install plugin into current site.\"\"\"\n        url = options['url']\n        user_mode = options['user']\n\n        # See the \"mode\" we need to operate in\n        install = options.get('install')\n        uninstall = options.get('uninstall')\n        upgrade = options.get('upgrade')\n        list_available = options.get('list')\n        list_installed = options.get('list_installed')\n        show_install_notes = options.get('show_install_notes', True)\n        command_count = [bool(x) for x in (\n            install,\n            uninstall,\n            upgrade,\n            list_available,\n            list_installed)].count(True)\n        if command_count > 1 or command_count == 0:\n            print(self.help())\n            return 2\n\n        if options.get('output_dir') is not None:\n            self.output_dir = options.get('output_dir')\n        else:\n            if not self.site.configured and not user_mode and install:\n                LOGGER.notice('No site found, assuming --user')\n                user_mode = True\n\n            if user_mode:\n                self.output_dir = os.path.expanduser('~/.nikola/plugins')\n            else:\n                self.output_dir = 'plugins'\n\n        if list_available:\n            return self.list_available(url)\n        elif list_installed:\n            return self.list_installed()\n        elif upgrade:\n            return self.do_upgrade(url)\n        elif uninstall:\n            return self.do_uninstall(uninstall)\n        elif install:\n            return self.do_install(url, install, show_install_notes)\n\n    def list_available(self, url):\n        \"\"\"List all available plugins.\"\"\"\n        data = self.get_json(url)\n        print(\"Available Plugins:\")\n        print(\"------------------\")\n        for plugin in sorted(data.keys()):\n            print(plugin)\n        return 0\n\n    def list_installed(self):\n        \"\"\"List installed plugins.\"\"\"\n        plugins = []\n        for plugin in self.site.plugin_manager.getAllPlugins():\n            p = plugin.path\n            if os.path.isdir(p):\n                p = p + os.sep\n            else:\n                p = p + '.py'\n            plugins.append([plugin.name, p])\n\n        plugins.sort()\n        print('Installed Plugins')\n        print('-----------------')\n        for name, path in plugins:\n            print('{0} at {1}'.format(name, path))\n        print('\\n\\nAlso, you have disabled these plugins: {}'.format(self.site.config['DISABLED_PLUGINS']))\n        return 0\n\n    def do_upgrade(self, url):\n        \"\"\"Upgrade all installed plugins.\"\"\"\n        LOGGER.warning('This is not very smart, it just reinstalls some plugins and hopes for the best')\n        data = self.get_json(url)\n        plugins = []\n        for plugin in self.site.plugin_manager.getAllPlugins():\n            p = plugin.path\n            if os.path.isdir(p):\n                p = p + os.sep\n            else:\n                p = p + '.py'\n            if plugin.name in data:\n                plugins.append([plugin.name, p])\n        print('Will upgrade {0} plugins: {1}'.format(len(plugins), ', '.join(n for n, _ in plugins)))\n        for name, path in plugins:\n            print('Upgrading {0}'.format(name))\n            p = path\n            while True:\n                tail, head = os.path.split(path)\n                if head == 'plugins':\n                    self.output_dir = path\n                    break\n                elif tail == '':\n                    LOGGER.error(\"Can't find the plugins folder for path: {0}\".format(p))\n                    return 1\n                else:\n                    path = tail\n            self.do_install(url, name)\n        return 0\n\n    def do_install(self, url, name, show_install_notes=True):\n        \"\"\"Download and install a plugin.\"\"\"\n        data = self.get_json(url)\n        if name in data:\n            utils.makedirs(self.output_dir)\n            url = data[name]\n            LOGGER.info(\"Downloading '{0}'\".format(url))\n            try:\n                zip_data = requests.get(url).content\n            except requests.exceptions.SSLError:\n                LOGGER.warning(\"SSL error, using http instead of https (press ^C to abort)\")\n                time.sleep(1)\n                url = url.replace('https', 'http', 1)\n                zip_data = requests.get(url).content\n\n            zip_file = io.BytesIO()\n            zip_file.write(zip_data)\n            LOGGER.info('Extracting: {0} into {1}/'.format(name, self.output_dir))\n            utils.extract_all(zip_file, self.output_dir)\n            dest_path = os.path.join(self.output_dir, name)\n        else:\n            try:\n                plugin_path = utils.get_plugin_path(name)\n            except:\n                LOGGER.error(\"Can't find plugin \" + name)\n                return 1\n\n            utils.makedirs(self.output_dir)\n            dest_path = os.path.join(self.output_dir, name)\n            if os.path.exists(dest_path):\n                LOGGER.error(\"{0} is already installed\".format(name))\n                return 1\n\n            LOGGER.info('Copying {0} into plugins'.format(plugin_path))\n            shutil.copytree(plugin_path, dest_path)\n\n        reqpath = os.path.join(dest_path, 'requirements.txt')\n        if os.path.exists(reqpath):\n            LOGGER.notice('This plugin has Python dependencies.')\n            LOGGER.info('Installing dependencies with pip...')\n            try:\n                subprocess.check_call((sys.executable, '-m', 'pip', 'install', '-r', reqpath))\n            except subprocess.CalledProcessError:\n                LOGGER.error('Could not install the dependencies.')\n                print('Contents of the requirements.txt file:\\n')\n                with io.open(reqpath, 'r', encoding='utf-8') as fh:\n                    print(utils.indent(fh.read(), 4 * ' '))\n                print('You have to install those yourself or through a '\n                      'package manager.')\n            else:\n                LOGGER.info('Dependency installation succeeded.')\n        reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')\n        if os.path.exists(reqnpypath):\n            LOGGER.notice('This plugin has third-party '\n                          'dependencies you need to install '\n                          'manually.')\n            print('Contents of the requirements-nonpy.txt file:\\n')\n            with io.open(reqnpypath, 'r', encoding='utf-8') as fh:\n                for l in fh.readlines():\n                    i, j = l.split('::')\n                    print(utils.indent(i.strip(), 4 * ' '))\n                    print(utils.indent(j.strip(), 8 * ' '))\n                    print()\n\n            print('You have to install those yourself or through a package '\n                  'manager.')\n        confpypath = os.path.join(dest_path, 'conf.py.sample')\n        if os.path.exists(confpypath) and show_install_notes:\n            LOGGER.notice('This plugin has a sample config file.  Integrate it with yours in order to make this plugin work!')\n            print('Contents of the conf.py.sample file:\\n')\n            with io.open(confpypath, 'r', encoding='utf-8') as fh:\n                if self.site.colorful:\n                    print(utils.indent(pygments.highlight(\n                        fh.read(), PythonLexer(), TerminalFormatter()),\n                        4 * ' '))\n                else:\n                    print(utils.indent(fh.read(), 4 * ' '))\n        return 0\n\n    def do_uninstall(self, name):\n        \"\"\"Uninstall a plugin.\"\"\"\n        for plugin in self.site.plugin_manager.getAllPlugins():  # FIXME: this is repeated thrice\n            p = plugin.path\n            if os.path.isdir(p):\n                p = p + os.sep\n            else:\n                p = os.path.dirname(p)\n            if name == plugin.name:  # Uninstall this one\n                LOGGER.warning('About to uninstall plugin: {0}'.format(name))\n                LOGGER.warning('This will delete {0}'.format(p))\n                sure = utils.ask_yesno('Are you sure?')\n                if sure:\n                    LOGGER.warning('Removing {0}'.format(p))\n                    shutil.rmtree(p)\n                    return 0\n                return 1\n        LOGGER.error('Unknown plugin: {0}'.format(name))\n        return 1\n\n    def get_json(self, url):\n        \"\"\"Download the JSON file with all plugins.\"\"\"\n        if self.json is None:\n            try:\n                self.json = requests.get(url).json()\n            except requests.exceptions.SSLError:\n                LOGGER.warning(\"SSL error, using http instead of https (press ^C to abort)\")\n                time.sleep(1)\n                url = url.replace('https', 'http', 1)\n                self.json = requests.get(url).json()\n        return self.json\n", "path": "nikola/plugins/command/plugin.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n\n# Copyright \u00a9 2012-2016 Roberto Alsina and others.\n\n# Permission is hereby granted, free of charge, to any\n# person obtaining a copy of this software and associated\n# documentation files (the \"Software\"), to deal in the\n# Software without restriction, including without limitation\n# the rights to use, copy, modify, merge, publish,\n# distribute, sublicense, and/or sell copies of the\n# Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice\n# shall be included in all copies or substantial portions of\n# the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY\n# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\n# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS\n# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR\n# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n\n\"\"\"Manage plugins.\"\"\"\n\nfrom __future__ import print_function\nimport io\nimport os\nimport sys\nimport shutil\nimport subprocess\nimport time\nimport requests\n\nimport pygments\nfrom pygments.lexers import PythonLexer\nfrom pygments.formatters import TerminalFormatter\n\nfrom nikola.plugin_categories import Command\nfrom nikola import utils\n\nLOGGER = utils.get_logger('plugin', utils.STDERR_HANDLER)\n\n\nclass CommandPlugin(Command):\n    \"\"\"Manage plugins.\"\"\"\n\n    json = None\n    name = \"plugin\"\n    doc_usage = \"[[-u][--user] --install name] | [[-u] [-l |--upgrade|--list-installed] | [--uninstall name]]\"\n    doc_purpose = \"manage plugins\"\n    output_dir = None\n    needs_config = False\n    cmd_options = [\n        {\n            'name': 'install',\n            'short': 'i',\n            'long': 'install',\n            'type': str,\n            'default': '',\n            'help': 'Install a plugin.',\n        },\n        {\n            'name': 'uninstall',\n            'long': 'uninstall',\n            'short': 'r',\n            'type': str,\n            'default': '',\n            'help': 'Uninstall a plugin.'\n        },\n        {\n            'name': 'list',\n            'short': 'l',\n            'long': 'list',\n            'type': bool,\n            'default': False,\n            'help': 'Show list of available plugins.'\n        },\n        {\n            'name': 'url',\n            'short': 'u',\n            'long': 'url',\n            'type': str,\n            'help': \"URL for the plugin repository (default: \"\n                    \"https://plugins.getnikola.com/v7/plugins.json)\",\n            'default': 'https://plugins.getnikola.com/v7/plugins.json'\n        },\n        {\n            'name': 'user',\n            'long': 'user',\n            'type': bool,\n            'help': \"Install user-wide, available for all sites.\",\n            'default': False\n        },\n        {\n            'name': 'upgrade',\n            'long': 'upgrade',\n            'type': bool,\n            'help': \"Upgrade all installed plugins.\",\n            'default': False\n        },\n        {\n            'name': 'list_installed',\n            'long': 'list-installed',\n            'type': bool,\n            'help': \"List the installed plugins with their location.\",\n            'default': False\n        },\n    ]\n\n    def _execute(self, options, args):\n        \"\"\"Install plugin into current site.\"\"\"\n        url = options['url']\n        user_mode = options['user']\n\n        # See the \"mode\" we need to operate in\n        install = options.get('install')\n        uninstall = options.get('uninstall')\n        upgrade = options.get('upgrade')\n        list_available = options.get('list')\n        list_installed = options.get('list_installed')\n        show_install_notes = options.get('show_install_notes', True)\n        command_count = [bool(x) for x in (\n            install,\n            uninstall,\n            upgrade,\n            list_available,\n            list_installed)].count(True)\n        if command_count > 1 or command_count == 0:\n            print(self.help())\n            return 2\n\n        if options.get('output_dir') is not None:\n            self.output_dir = options.get('output_dir')\n        else:\n            if not self.site.configured and not user_mode and install:\n                LOGGER.notice('No site found, assuming --user')\n                user_mode = True\n\n            if user_mode:\n                self.output_dir = os.path.expanduser('~/.nikola/plugins')\n            else:\n                self.output_dir = 'plugins'\n\n        if list_available:\n            return self.list_available(url)\n        elif list_installed:\n            return self.list_installed()\n        elif upgrade:\n            return self.do_upgrade(url)\n        elif uninstall:\n            return self.do_uninstall(uninstall)\n        elif install:\n            return self.do_install(url, install, show_install_notes)\n\n    def list_available(self, url):\n        \"\"\"List all available plugins.\"\"\"\n        data = self.get_json(url)\n        print(\"Available Plugins:\")\n        print(\"------------------\")\n        for plugin in sorted(data.keys()):\n            print(plugin)\n        return 0\n\n    def list_installed(self):\n        \"\"\"List installed plugins.\"\"\"\n        plugins = []\n        for plugin in self.site.plugin_manager.getAllPlugins():\n            p = plugin.path\n            if os.path.isdir(p):\n                p = p + os.sep\n            else:\n                p = p + '.py'\n            plugins.append([plugin.name, p])\n\n        plugins.sort()\n        print('Installed Plugins')\n        print('-----------------')\n        for name, path in plugins:\n            print('{0} at {1}'.format(name, path))\n        print('\\n\\nAlso, you have disabled these plugins: {}'.format(self.site.config['DISABLED_PLUGINS']))\n        return 0\n\n    def do_upgrade(self, url):\n        \"\"\"Upgrade all installed plugins.\"\"\"\n        LOGGER.warning('This is not very smart, it just reinstalls some plugins and hopes for the best')\n        data = self.get_json(url)\n        plugins = []\n        for plugin in self.site.plugin_manager.getAllPlugins():\n            p = plugin.path\n            if os.path.isdir(p):\n                p = p + os.sep\n            else:\n                p = p + '.py'\n            if plugin.name in data:\n                plugins.append([plugin.name, p])\n        print('Will upgrade {0} plugins: {1}'.format(len(plugins), ', '.join(n for n, _ in plugins)))\n        for name, path in plugins:\n            print('Upgrading {0}'.format(name))\n            p = path\n            while True:\n                tail, head = os.path.split(path)\n                if head == 'plugins':\n                    self.output_dir = path\n                    break\n                elif tail == '':\n                    LOGGER.error(\"Can't find the plugins folder for path: {0}\".format(p))\n                    return 1\n                else:\n                    path = tail\n            self.do_install(url, name)\n        return 0\n\n    def do_install(self, url, name, show_install_notes=True):\n        \"\"\"Download and install a plugin.\"\"\"\n        data = self.get_json(url)\n        if name in data:\n            utils.makedirs(self.output_dir)\n            url = data[name]\n            LOGGER.info(\"Downloading '{0}'\".format(url))\n            try:\n                zip_data = requests.get(url).content\n            except requests.exceptions.SSLError:\n                LOGGER.warning(\"SSL error, using http instead of https (press ^C to abort)\")\n                time.sleep(1)\n                url = url.replace('https', 'http', 1)\n                zip_data = requests.get(url).content\n\n            zip_file = io.BytesIO()\n            zip_file.write(zip_data)\n            LOGGER.info('Extracting: {0} into {1}/'.format(name, self.output_dir))\n            utils.extract_all(zip_file, self.output_dir)\n            dest_path = os.path.join(self.output_dir, name)\n        else:\n            try:\n                plugin_path = utils.get_plugin_path(name)\n            except:\n                LOGGER.error(\"Can't find plugin \" + name)\n                return 1\n\n            utils.makedirs(self.output_dir)\n            dest_path = os.path.join(self.output_dir, name)\n            if os.path.exists(dest_path):\n                LOGGER.error(\"{0} is already installed\".format(name))\n                return 1\n\n            LOGGER.info('Copying {0} into plugins'.format(plugin_path))\n            shutil.copytree(plugin_path, dest_path)\n\n        reqpath = os.path.join(dest_path, 'requirements.txt')\n        if os.path.exists(reqpath):\n            LOGGER.notice('This plugin has Python dependencies.')\n            LOGGER.info('Installing dependencies with pip...')\n            try:\n                subprocess.check_call((sys.executable, '-m', 'pip', 'install', '-r', reqpath))\n            except subprocess.CalledProcessError:\n                LOGGER.error('Could not install the dependencies.')\n                print('Contents of the requirements.txt file:\\n')\n                with io.open(reqpath, 'r', encoding='utf-8') as fh:\n                    print(utils.indent(fh.read(), 4 * ' '))\n                print('You have to install those yourself or through a '\n                      'package manager.')\n            else:\n                LOGGER.info('Dependency installation succeeded.')\n        reqnpypath = os.path.join(dest_path, 'requirements-nonpy.txt')\n        if os.path.exists(reqnpypath):\n            LOGGER.notice('This plugin has third-party '\n                          'dependencies you need to install '\n                          'manually.')\n            print('Contents of the requirements-nonpy.txt file:\\n')\n            with io.open(reqnpypath, 'r', encoding='utf-8') as fh:\n                for l in fh.readlines():\n                    i, j = l.split('::')\n                    print(utils.indent(i.strip(), 4 * ' '))\n                    print(utils.indent(j.strip(), 8 * ' '))\n                    print()\n\n            print('You have to install those yourself or through a package '\n                  'manager.')\n        confpypath = os.path.join(dest_path, 'conf.py.sample')\n        if os.path.exists(confpypath) and show_install_notes:\n            LOGGER.notice('This plugin has a sample config file.  Integrate it with yours in order to make this plugin work!')\n            print('Contents of the conf.py.sample file:\\n')\n            with io.open(confpypath, 'r', encoding='utf-8') as fh:\n                if self.site.colorful:\n                    print(utils.indent(pygments.highlight(\n                        fh.read(), PythonLexer(), TerminalFormatter()),\n                        4 * ' '))\n                else:\n                    print(utils.indent(fh.read(), 4 * ' '))\n        return 0\n\n    def do_uninstall(self, name):\n        \"\"\"Uninstall a plugin.\"\"\"\n        for plugin in self.site.plugin_manager.getAllPlugins():  # FIXME: this is repeated thrice\n            if name == plugin.name:  # Uninstall this one\n                p = plugin.path\n                if os.path.isdir(p):\n                    # Plugins that have a package in them need to delete parent\n                    # Issue #2356\n                    p = p + os.sep\n                    p = os.path.abspath(os.path.join(p, os.pardir))\n                else:\n                    p = os.path.dirname(p)\n                LOGGER.warning('About to uninstall plugin: {0}'.format(name))\n                LOGGER.warning('This will delete {0}'.format(p))\n                sure = utils.ask_yesno('Are you sure?')\n                if sure:\n                    LOGGER.warning('Removing {0}'.format(p))\n                    shutil.rmtree(p)\n                    return 0\n                return 1\n        LOGGER.error('Unknown plugin: {0}'.format(name))\n        return 1\n\n    def get_json(self, url):\n        \"\"\"Download the JSON file with all plugins.\"\"\"\n        if self.json is None:\n            try:\n                self.json = requests.get(url).json()\n            except requests.exceptions.SSLError:\n                LOGGER.warning(\"SSL error, using http instead of https (press ^C to abort)\")\n                time.sleep(1)\n                url = url.replace('https', 'http', 1)\n                self.json = requests.get(url).json()\n        return self.json\n", "path": "nikola/plugins/command/plugin.py"}]}