We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Unicode issues with exec plugin
Same as https://github.com/Flexget/Flexget/issues/1269, but with exec plugin

It was working well recently, but now if the given path contains unicode (e. g. "ñ") characters it crashes.

Running flexget version 2.1.23 installed using pip, on Windows 8.1 x64 + Python 2.7.12

Relevant snippet of crash report (crash_report.2016.07.25.115814396000.log): 

```
2016-07-25 11:58 CRITICAL task          remove-folders  BUG: Unhandled error in plugin exec: 'ascii' codec can't encode character u'\xf1' in position 124: ordinal not in range(128)
Traceback (most recent call last):
  File "c:\python27\lib\site-packages\flexget\task.py", line 444, in __run_plugin
    return method(*args, **kwargs)
  File "c:\python27\lib\site-packages\flexget\event.py", line 23, in __call__
    return self.func(*args, **kwargs)
  File "c:\python27\lib\site-packages\flexget\plugins\output\exec.py", line 192, in phase_handler
    self.execute(task, 'on_' + phase, config)
  File "c:\python27\lib\site-packages\flexget\plugins\output\exec.py", line 163, in execute
    if (self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and
  File "c:\python27\lib\site-packages\flexget\plugins\output\exec.py", line 109, in execute_cmd
    stderr=subprocess.STDOUT, close_fds=False)
  File "c:\python27\lib\subprocess.py", line 711, in __init__
    errread, errwrite)
  File "c:\python27\lib\subprocess.py", line 929, in _execute_child
    args = '{} /c "{}"'.format (comspec, args)
UnicodeEncodeError: 'ascii' codec can't encode character u'\xf1' in position 124: ordinal not in range(128)
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `flexget/plugins/output/exec.py`
Content:
```
1 from __future__ import unicode_literals, division, absolute_import
2 from builtins import *  # pylint: disable=unused-import, redefined-builtin
3 from past.builtins import basestring
4 
5 import logging
6 import subprocess
7 
8 from flexget import plugin
9 from flexget.entry import Entry
10 from flexget.event import event
11 from flexget.config_schema import one_or_more
12 from flexget.utils.template import render_from_entry, render_from_task, RenderError
13 from flexget.utils.tools import io_encoding, native_str_to_text
14 
15 log = logging.getLogger('exec')
16 
17 
18 class EscapingEntry(Entry):
19     """Helper class, same as a Entry, but returns all string value with quotes escaped."""
20 
21     def __init__(self, entry):
22         super(EscapingEntry, self).__init__(entry)
23 
24     def __getitem__(self, key):
25         value = super(EscapingEntry, self).__getitem__(key)
26         # TODO: May need to be different depending on OS
27         if isinstance(value, basestring):
28             value = value.replace('"', '\\"')
29         return value
30 
31 
32 class PluginExec(object):
33     """
34     Execute commands
35 
36     Simple example, xecute command for entries that reach output::
37 
38       exec: echo 'found {{title}} at {{url}}' > file
39 
40     Advanced Example::
41 
42       exec:
43         on_start:
44           phase: echo "Started"
45         on_input:
46           for_entries: echo 'got {{title}}'
47         on_output:
48           for_accepted: echo 'accepted {{title}} - {{url}} > file
49 
50     You can use all (available) entry fields in the command.
51     """
52 
53     NAME = 'exec'
54     HANDLED_PHASES = ['start', 'input', 'filter', 'output', 'exit']
55 
56     schema = {
57         'oneOf': [
58             one_or_more({'type': 'string'}),
59             {
60                 'type': 'object',
61                 'properties': {
62                     'on_start': {'$ref': '#/definitions/phaseSettings'},
63                     'on_input': {'$ref': '#/definitions/phaseSettings'},
64                     'on_filter': {'$ref': '#/definitions/phaseSettings'},
65                     'on_output': {'$ref': '#/definitions/phaseSettings'},
66                     'on_exit': {'$ref': '#/definitions/phaseSettings'},
67                     'fail_entries': {'type': 'boolean'},
68                     'auto_escape': {'type': 'boolean'},
69                     'encoding': {'type': 'string'},
70                     'allow_background': {'type': 'boolean'}
71                 },
72                 'additionalProperties': False
73             }
74         ],
75         'definitions': {
76             'phaseSettings': {
77                 'type': 'object',
78                 'properties': {
79                     'phase': one_or_more({'type': 'string'}),
80                     'for_entries': one_or_more({'type': 'string'}),
81                     'for_accepted': one_or_more({'type': 'string'}),
82                     'for_rejected': one_or_more({'type': 'string'}),
83                     'for_failed': one_or_more({'type': 'string'})
84                 },
85                 'additionalProperties': False
86             }
87         }
88     }
89 
90     def prepare_config(self, config):
91         if isinstance(config, basestring):
92             config = [config]
93         if isinstance(config, list):
94             config = {'on_output': {'for_accepted': config}}
95         if not config.get('encoding'):
96             config['encoding'] = io_encoding
97         for phase_name in config:
98             if phase_name.startswith('on_'):
99                 for items_name in config[phase_name]:
100                     if isinstance(config[phase_name][items_name], basestring):
101                         config[phase_name][items_name] = [config[phase_name][items_name]]
102 
103         return config
104 
105     def execute_cmd(self, cmd, allow_background, encoding):
106         log.verbose('Executing: %s' % cmd)
107         # if PY2: cmd = cmd.encode(encoding) ?
108         p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
109                              stderr=subprocess.STDOUT, close_fds=False)
110         if not allow_background:
111             (r, w) = (p.stdout, p.stdin)
112             response = native_str_to_text(r.read(), encoding=encoding, errors='replace')
113             r.close()
114             w.close()
115             if response:
116                 log.info('Stdout: %s' % response)
117         return p.wait()
118 
119     def execute(self, task, phase_name, config):
120         config = self.prepare_config(config)
121         if phase_name not in config:
122             log.debug('phase %s not configured' % phase_name)
123             return
124 
125         name_map = {'for_entries': task.entries, 'for_accepted': task.accepted,
126                     'for_rejected': task.rejected, 'for_failed': task.failed}
127 
128         allow_background = config.get('allow_background')
129         for operation, entries in name_map.items():
130             if operation not in config[phase_name]:
131                 continue
132 
133             log.debug('running phase_name: %s operation: %s entries: %s' % (phase_name, operation, len(entries)))
134 
135             for entry in entries:
136                 for cmd in config[phase_name][operation]:
137                     entrydict = EscapingEntry(entry) if config.get('auto_escape') else entry
138                     # Do string replacement from entry, but make sure quotes get escaped
139                     try:
140                         cmd = render_from_entry(cmd, entrydict)
141                     except RenderError as e:
142                         log.error('Could not set exec command for %s: %s' % (entry['title'], e))
143                         # fail the entry if configured to do so
144                         if config.get('fail_entries'):
145                             entry.fail('Entry `%s` does not have required fields for string replacement.' %
146                                        entry['title'])
147                         continue
148 
149                     log.debug('phase_name: %s operation: %s cmd: %s' % (phase_name, operation, cmd))
150                     if task.options.test:
151                         log.info('Would execute: %s' % cmd)
152                     else:
153                         # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,
154                         # so logging continues to work.
155                         try:
156                             cmd.encode(config['encoding'])
157                         except UnicodeEncodeError:
158                             log.error('Unable to encode cmd `%s` to %s' % (cmd, config['encoding']))
159                             if config.get('fail_entries'):
160                                 entry.fail('cmd `%s` could not be encoded to %s.' % (cmd, config['encoding']))
161                             continue
162                         # Run the command, fail entries with non-zero return code if configured to
163                         if (self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and
164                                 config.get('fail_entries')):
165                             entry.fail('exec return code was non-zero')
166 
167         # phase keyword in this
168         if 'phase' in config[phase_name]:
169             for cmd in config[phase_name]['phase']:
170                 try:
171                     cmd = render_from_task(cmd, task)
172                 except RenderError as e:
173                     log.error('Error rendering `%s`: %s' % (cmd, e))
174                 else:
175                     log.debug('phase cmd: %s' % cmd)
176                     if task.options.test:
177                         log.info('Would execute: %s' % cmd)
178                     else:
179                         self.execute_cmd(cmd, allow_background, config['encoding'])
180 
181     def __getattr__(self, item):
182         """Creates methods to handle task phases."""
183         for phase in self.HANDLED_PHASES:
184             if item == plugin.phase_methods[phase]:
185                 # A phase method we handle has been requested
186                 break
187         else:
188             # We don't handle this phase
189             raise AttributeError(item)
190 
191         def phase_handler(task, config):
192             self.execute(task, 'on_' + phase, config)
193 
194         # Make sure we run after other plugins so exec can use their output
195         phase_handler.priority = 100
196         return phase_handler
197 
198 
199 @event('plugin.register')
200 def register_plugin():
201     plugin.register(PluginExec, 'exec', api_ver=2)
202
```
--- 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/flexget/plugins/output/exec.py b/flexget/plugins/output/exec.py
--- a/flexget/plugins/output/exec.py
+++ b/flexget/plugins/output/exec.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals, division, absolute_import
 from builtins import *  # pylint: disable=unused-import, redefined-builtin
 from past.builtins import basestring
+from future.utils import text_to_native_str
 
 import logging
 import subprocess
@@ -10,7 +11,7 @@
 from flexget.event import event
 from flexget.config_schema import one_or_more
 from flexget.utils.template import render_from_entry, render_from_task, RenderError
-from flexget.utils.tools import io_encoding, native_str_to_text
+from flexget.utils.tools import io_encoding
 
 log = logging.getLogger('exec')
 
@@ -103,17 +104,16 @@
         return config
 
     def execute_cmd(self, cmd, allow_background, encoding):
-        log.verbose('Executing: %s' % cmd)
-        # if PY2: cmd = cmd.encode(encoding) ?
-        p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                             stderr=subprocess.STDOUT, close_fds=False)
+        log.verbose('Executing: %s', cmd)
+        p = subprocess.Popen(text_to_native_str(cmd, encoding=io_encoding), shell=True, stdin=subprocess.PIPE,
+                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=False)
         if not allow_background:
-            (r, w) = (p.stdout, p.stdin)
-            response = native_str_to_text(r.read(), encoding=encoding, errors='replace')
+            r, w = (p.stdout, p.stdin)
+            response = r.read().decode(io_encoding)
             r.close()
             w.close()
             if response:
-                log.info('Stdout: %s' % response)
+                log.info('Stdout: %s', response.rstrip())  # rstrip to get rid of newlines
         return p.wait()
 
     def execute(self, task, phase_name, config):

{"golden_diff": "diff --git a/flexget/plugins/output/exec.py b/flexget/plugins/output/exec.py\n--- a/flexget/plugins/output/exec.py\n+++ b/flexget/plugins/output/exec.py\n@@ -1,6 +1,7 @@\n from __future__ import unicode_literals, division, absolute_import\n from builtins import *  # pylint: disable=unused-import, redefined-builtin\n from past.builtins import basestring\n+from future.utils import text_to_native_str\n \n import logging\n import subprocess\n@@ -10,7 +11,7 @@\n from flexget.event import event\n from flexget.config_schema import one_or_more\n from flexget.utils.template import render_from_entry, render_from_task, RenderError\n-from flexget.utils.tools import io_encoding, native_str_to_text\n+from flexget.utils.tools import io_encoding\n \n log = logging.getLogger('exec')\n \n@@ -103,17 +104,16 @@\n         return config\n \n     def execute_cmd(self, cmd, allow_background, encoding):\n-        log.verbose('Executing: %s' % cmd)\n-        # if PY2: cmd = cmd.encode(encoding) ?\n-        p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,\n-                             stderr=subprocess.STDOUT, close_fds=False)\n+        log.verbose('Executing: %s', cmd)\n+        p = subprocess.Popen(text_to_native_str(cmd, encoding=io_encoding), shell=True, stdin=subprocess.PIPE,\n+                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=False)\n         if not allow_background:\n-            (r, w) = (p.stdout, p.stdin)\n-            response = native_str_to_text(r.read(), encoding=encoding, errors='replace')\n+            r, w = (p.stdout, p.stdin)\n+            response = r.read().decode(io_encoding)\n             r.close()\n             w.close()\n             if response:\n-                log.info('Stdout: %s' % response)\n+                log.info('Stdout: %s', response.rstrip())  # rstrip to get rid of newlines\n         return p.wait()\n \n     def execute(self, task, phase_name, config):\n", "issue": "Unicode issues with exec plugin\nSame as https://github.com/Flexget/Flexget/issues/1269, but with exec plugin\n\nIt was working well recently, but now if the given path contains unicode (e. g. \"\u00f1\") characters it crashes.\n\nRunning flexget version 2.1.23 installed using pip, on Windows 8.1 x64 + Python 2.7.12\n\nRelevant snippet of crash report (crash_report.2016.07.25.115814396000.log): \n\n```\n2016-07-25 11:58 CRITICAL task          remove-folders  BUG: Unhandled error in plugin exec: 'ascii' codec can't encode character u'\\xf1' in position 124: ordinal not in range(128)\nTraceback (most recent call last):\n  File \"c:\\python27\\lib\\site-packages\\flexget\\task.py\", line 444, in __run_plugin\n    return method(*args, **kwargs)\n  File \"c:\\python27\\lib\\site-packages\\flexget\\event.py\", line 23, in __call__\n    return self.func(*args, **kwargs)\n  File \"c:\\python27\\lib\\site-packages\\flexget\\plugins\\output\\exec.py\", line 192, in phase_handler\n    self.execute(task, 'on_' + phase, config)\n  File \"c:\\python27\\lib\\site-packages\\flexget\\plugins\\output\\exec.py\", line 163, in execute\n    if (self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and\n  File \"c:\\python27\\lib\\site-packages\\flexget\\plugins\\output\\exec.py\", line 109, in execute_cmd\n    stderr=subprocess.STDOUT, close_fds=False)\n  File \"c:\\python27\\lib\\subprocess.py\", line 711, in __init__\n    errread, errwrite)\n  File \"c:\\python27\\lib\\subprocess.py\", line 929, in _execute_child\n    args = '{} /c \"{}\"'.format (comspec, args)\nUnicodeEncodeError: 'ascii' codec can't encode character u'\\xf1' in position 124: ordinal not in range(128)\n```\n\n", "before_files": [{"content": "from __future__ import unicode_literals, division, absolute_import\nfrom builtins import *  # pylint: disable=unused-import, redefined-builtin\nfrom past.builtins import basestring\n\nimport logging\nimport subprocess\n\nfrom flexget import plugin\nfrom flexget.entry import Entry\nfrom flexget.event import event\nfrom flexget.config_schema import one_or_more\nfrom flexget.utils.template import render_from_entry, render_from_task, RenderError\nfrom flexget.utils.tools import io_encoding, native_str_to_text\n\nlog = logging.getLogger('exec')\n\n\nclass EscapingEntry(Entry):\n    \"\"\"Helper class, same as a Entry, but returns all string value with quotes escaped.\"\"\"\n\n    def __init__(self, entry):\n        super(EscapingEntry, self).__init__(entry)\n\n    def __getitem__(self, key):\n        value = super(EscapingEntry, self).__getitem__(key)\n        # TODO: May need to be different depending on OS\n        if isinstance(value, basestring):\n            value = value.replace('\"', '\\\\\"')\n        return value\n\n\nclass PluginExec(object):\n    \"\"\"\n    Execute commands\n\n    Simple example, xecute command for entries that reach output::\n\n      exec: echo 'found {{title}} at {{url}}' > file\n\n    Advanced Example::\n\n      exec:\n        on_start:\n          phase: echo \"Started\"\n        on_input:\n          for_entries: echo 'got {{title}}'\n        on_output:\n          for_accepted: echo 'accepted {{title}} - {{url}} > file\n\n    You can use all (available) entry fields in the command.\n    \"\"\"\n\n    NAME = 'exec'\n    HANDLED_PHASES = ['start', 'input', 'filter', 'output', 'exit']\n\n    schema = {\n        'oneOf': [\n            one_or_more({'type': 'string'}),\n            {\n                'type': 'object',\n                'properties': {\n                    'on_start': {'$ref': '#/definitions/phaseSettings'},\n                    'on_input': {'$ref': '#/definitions/phaseSettings'},\n                    'on_filter': {'$ref': '#/definitions/phaseSettings'},\n                    'on_output': {'$ref': '#/definitions/phaseSettings'},\n                    'on_exit': {'$ref': '#/definitions/phaseSettings'},\n                    'fail_entries': {'type': 'boolean'},\n                    'auto_escape': {'type': 'boolean'},\n                    'encoding': {'type': 'string'},\n                    'allow_background': {'type': 'boolean'}\n                },\n                'additionalProperties': False\n            }\n        ],\n        'definitions': {\n            'phaseSettings': {\n                'type': 'object',\n                'properties': {\n                    'phase': one_or_more({'type': 'string'}),\n                    'for_entries': one_or_more({'type': 'string'}),\n                    'for_accepted': one_or_more({'type': 'string'}),\n                    'for_rejected': one_or_more({'type': 'string'}),\n                    'for_failed': one_or_more({'type': 'string'})\n                },\n                'additionalProperties': False\n            }\n        }\n    }\n\n    def prepare_config(self, config):\n        if isinstance(config, basestring):\n            config = [config]\n        if isinstance(config, list):\n            config = {'on_output': {'for_accepted': config}}\n        if not config.get('encoding'):\n            config['encoding'] = io_encoding\n        for phase_name in config:\n            if phase_name.startswith('on_'):\n                for items_name in config[phase_name]:\n                    if isinstance(config[phase_name][items_name], basestring):\n                        config[phase_name][items_name] = [config[phase_name][items_name]]\n\n        return config\n\n    def execute_cmd(self, cmd, allow_background, encoding):\n        log.verbose('Executing: %s' % cmd)\n        # if PY2: cmd = cmd.encode(encoding) ?\n        p = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE,\n                             stderr=subprocess.STDOUT, close_fds=False)\n        if not allow_background:\n            (r, w) = (p.stdout, p.stdin)\n            response = native_str_to_text(r.read(), encoding=encoding, errors='replace')\n            r.close()\n            w.close()\n            if response:\n                log.info('Stdout: %s' % response)\n        return p.wait()\n\n    def execute(self, task, phase_name, config):\n        config = self.prepare_config(config)\n        if phase_name not in config:\n            log.debug('phase %s not configured' % phase_name)\n            return\n\n        name_map = {'for_entries': task.entries, 'for_accepted': task.accepted,\n                    'for_rejected': task.rejected, 'for_failed': task.failed}\n\n        allow_background = config.get('allow_background')\n        for operation, entries in name_map.items():\n            if operation not in config[phase_name]:\n                continue\n\n            log.debug('running phase_name: %s operation: %s entries: %s' % (phase_name, operation, len(entries)))\n\n            for entry in entries:\n                for cmd in config[phase_name][operation]:\n                    entrydict = EscapingEntry(entry) if config.get('auto_escape') else entry\n                    # Do string replacement from entry, but make sure quotes get escaped\n                    try:\n                        cmd = render_from_entry(cmd, entrydict)\n                    except RenderError as e:\n                        log.error('Could not set exec command for %s: %s' % (entry['title'], e))\n                        # fail the entry if configured to do so\n                        if config.get('fail_entries'):\n                            entry.fail('Entry `%s` does not have required fields for string replacement.' %\n                                       entry['title'])\n                        continue\n\n                    log.debug('phase_name: %s operation: %s cmd: %s' % (phase_name, operation, cmd))\n                    if task.options.test:\n                        log.info('Would execute: %s' % cmd)\n                    else:\n                        # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,\n                        # so logging continues to work.\n                        try:\n                            cmd.encode(config['encoding'])\n                        except UnicodeEncodeError:\n                            log.error('Unable to encode cmd `%s` to %s' % (cmd, config['encoding']))\n                            if config.get('fail_entries'):\n                                entry.fail('cmd `%s` could not be encoded to %s.' % (cmd, config['encoding']))\n                            continue\n                        # Run the command, fail entries with non-zero return code if configured to\n                        if (self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and\n                                config.get('fail_entries')):\n                            entry.fail('exec return code was non-zero')\n\n        # phase keyword in this\n        if 'phase' in config[phase_name]:\n            for cmd in config[phase_name]['phase']:\n                try:\n                    cmd = render_from_task(cmd, task)\n                except RenderError as e:\n                    log.error('Error rendering `%s`: %s' % (cmd, e))\n                else:\n                    log.debug('phase cmd: %s' % cmd)\n                    if task.options.test:\n                        log.info('Would execute: %s' % cmd)\n                    else:\n                        self.execute_cmd(cmd, allow_background, config['encoding'])\n\n    def __getattr__(self, item):\n        \"\"\"Creates methods to handle task phases.\"\"\"\n        for phase in self.HANDLED_PHASES:\n            if item == plugin.phase_methods[phase]:\n                # A phase method we handle has been requested\n                break\n        else:\n            # We don't handle this phase\n            raise AttributeError(item)\n\n        def phase_handler(task, config):\n            self.execute(task, 'on_' + phase, config)\n\n        # Make sure we run after other plugins so exec can use their output\n        phase_handler.priority = 100\n        return phase_handler\n\n\n@event('plugin.register')\ndef register_plugin():\n    plugin.register(PluginExec, 'exec', api_ver=2)\n", "path": "flexget/plugins/output/exec.py"}], "after_files": [{"content": "from __future__ import unicode_literals, division, absolute_import\nfrom builtins import *  # pylint: disable=unused-import, redefined-builtin\nfrom past.builtins import basestring\nfrom future.utils import text_to_native_str\n\nimport logging\nimport subprocess\n\nfrom flexget import plugin\nfrom flexget.entry import Entry\nfrom flexget.event import event\nfrom flexget.config_schema import one_or_more\nfrom flexget.utils.template import render_from_entry, render_from_task, RenderError\nfrom flexget.utils.tools import io_encoding\n\nlog = logging.getLogger('exec')\n\n\nclass EscapingEntry(Entry):\n    \"\"\"Helper class, same as a Entry, but returns all string value with quotes escaped.\"\"\"\n\n    def __init__(self, entry):\n        super(EscapingEntry, self).__init__(entry)\n\n    def __getitem__(self, key):\n        value = super(EscapingEntry, self).__getitem__(key)\n        # TODO: May need to be different depending on OS\n        if isinstance(value, basestring):\n            value = value.replace('\"', '\\\\\"')\n        return value\n\n\nclass PluginExec(object):\n    \"\"\"\n    Execute commands\n\n    Simple example, xecute command for entries that reach output::\n\n      exec: echo 'found {{title}} at {{url}}' > file\n\n    Advanced Example::\n\n      exec:\n        on_start:\n          phase: echo \"Started\"\n        on_input:\n          for_entries: echo 'got {{title}}'\n        on_output:\n          for_accepted: echo 'accepted {{title}} - {{url}} > file\n\n    You can use all (available) entry fields in the command.\n    \"\"\"\n\n    NAME = 'exec'\n    HANDLED_PHASES = ['start', 'input', 'filter', 'output', 'exit']\n\n    schema = {\n        'oneOf': [\n            one_or_more({'type': 'string'}),\n            {\n                'type': 'object',\n                'properties': {\n                    'on_start': {'$ref': '#/definitions/phaseSettings'},\n                    'on_input': {'$ref': '#/definitions/phaseSettings'},\n                    'on_filter': {'$ref': '#/definitions/phaseSettings'},\n                    'on_output': {'$ref': '#/definitions/phaseSettings'},\n                    'on_exit': {'$ref': '#/definitions/phaseSettings'},\n                    'fail_entries': {'type': 'boolean'},\n                    'auto_escape': {'type': 'boolean'},\n                    'encoding': {'type': 'string'},\n                    'allow_background': {'type': 'boolean'}\n                },\n                'additionalProperties': False\n            }\n        ],\n        'definitions': {\n            'phaseSettings': {\n                'type': 'object',\n                'properties': {\n                    'phase': one_or_more({'type': 'string'}),\n                    'for_entries': one_or_more({'type': 'string'}),\n                    'for_accepted': one_or_more({'type': 'string'}),\n                    'for_rejected': one_or_more({'type': 'string'}),\n                    'for_failed': one_or_more({'type': 'string'})\n                },\n                'additionalProperties': False\n            }\n        }\n    }\n\n    def prepare_config(self, config):\n        if isinstance(config, basestring):\n            config = [config]\n        if isinstance(config, list):\n            config = {'on_output': {'for_accepted': config}}\n        if not config.get('encoding'):\n            config['encoding'] = io_encoding\n        for phase_name in config:\n            if phase_name.startswith('on_'):\n                for items_name in config[phase_name]:\n                    if isinstance(config[phase_name][items_name], basestring):\n                        config[phase_name][items_name] = [config[phase_name][items_name]]\n\n        return config\n\n    def execute_cmd(self, cmd, allow_background, encoding):\n        log.verbose('Executing: %s', cmd)\n        p = subprocess.Popen(text_to_native_str(cmd, encoding=io_encoding), shell=True, stdin=subprocess.PIPE,\n                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=False)\n        if not allow_background:\n            r, w = (p.stdout, p.stdin)\n            response = r.read().decode(io_encoding)\n            r.close()\n            w.close()\n            if response:\n                log.info('Stdout: %s', response.rstrip())  # rstrip to get rid of newlines\n        return p.wait()\n\n    def execute(self, task, phase_name, config):\n        config = self.prepare_config(config)\n        if phase_name not in config:\n            log.debug('phase %s not configured' % phase_name)\n            return\n\n        name_map = {'for_entries': task.entries, 'for_accepted': task.accepted,\n                    'for_rejected': task.rejected, 'for_failed': task.failed}\n\n        allow_background = config.get('allow_background')\n        for operation, entries in name_map.items():\n            if operation not in config[phase_name]:\n                continue\n\n            log.debug('running phase_name: %s operation: %s entries: %s' % (phase_name, operation, len(entries)))\n\n            for entry in entries:\n                for cmd in config[phase_name][operation]:\n                    entrydict = EscapingEntry(entry) if config.get('auto_escape') else entry\n                    # Do string replacement from entry, but make sure quotes get escaped\n                    try:\n                        cmd = render_from_entry(cmd, entrydict)\n                    except RenderError as e:\n                        log.error('Could not set exec command for %s: %s' % (entry['title'], e))\n                        # fail the entry if configured to do so\n                        if config.get('fail_entries'):\n                            entry.fail('Entry `%s` does not have required fields for string replacement.' %\n                                       entry['title'])\n                        continue\n\n                    log.debug('phase_name: %s operation: %s cmd: %s' % (phase_name, operation, cmd))\n                    if task.options.test:\n                        log.info('Would execute: %s' % cmd)\n                    else:\n                        # Make sure the command can be encoded into appropriate encoding, don't actually encode yet,\n                        # so logging continues to work.\n                        try:\n                            cmd.encode(config['encoding'])\n                        except UnicodeEncodeError:\n                            log.error('Unable to encode cmd `%s` to %s' % (cmd, config['encoding']))\n                            if config.get('fail_entries'):\n                                entry.fail('cmd `%s` could not be encoded to %s.' % (cmd, config['encoding']))\n                            continue\n                        # Run the command, fail entries with non-zero return code if configured to\n                        if (self.execute_cmd(cmd, allow_background, config['encoding']) != 0 and\n                                config.get('fail_entries')):\n                            entry.fail('exec return code was non-zero')\n\n        # phase keyword in this\n        if 'phase' in config[phase_name]:\n            for cmd in config[phase_name]['phase']:\n                try:\n                    cmd = render_from_task(cmd, task)\n                except RenderError as e:\n                    log.error('Error rendering `%s`: %s' % (cmd, e))\n                else:\n                    log.debug('phase cmd: %s' % cmd)\n                    if task.options.test:\n                        log.info('Would execute: %s' % cmd)\n                    else:\n                        self.execute_cmd(cmd, allow_background, config['encoding'])\n\n    def __getattr__(self, item):\n        \"\"\"Creates methods to handle task phases.\"\"\"\n        for phase in self.HANDLED_PHASES:\n            if item == plugin.phase_methods[phase]:\n                # A phase method we handle has been requested\n                break\n        else:\n            # We don't handle this phase\n            raise AttributeError(item)\n\n        def phase_handler(task, config):\n            self.execute(task, 'on_' + phase, config)\n\n        # Make sure we run after other plugins so exec can use their output\n        phase_handler.priority = 100\n        return phase_handler\n\n\n@event('plugin.register')\ndef register_plugin():\n    plugin.register(PluginExec, 'exec', api_ver=2)\n", "path": "flexget/plugins/output/exec.py"}]}