We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Command line arguments for plugins are broken
It seems the command line arguments for plugins created in `add_parser_arguments(cls, add):` cant be extracted with `self.conf("argument_name")`. I suspect something with the plugin namespace prefix is going wrong.

My code is here https://github.com/e00E/lets-encrypt-icecast/blob/master/icecast.py .
In line 129 I add an argument via `add("configuration_file")`
and I try to use it in line 150 like so

```
self.config_file = self.conf("configuration_file")
if self.config_file is not None and not os.path.isfile(self.config_file):
    raise PluginError('User supplied icecast configuration file does not exist.')
```

When running `letsencrypt install` it crashes with the last log message

```
Traceback (most recent call last):
  File "./letsencrypt/venv/bin/letsencrypt", line 9, in <module>
    load_entry_point('letsencrypt==0.1', 'console_scripts', 'letsencrypt')()
  File "/home/e/icecast/letsencrypt/letsencrypt/cli.py", line 778, in main
    return args.func(args, config, plugins)
  File "/home/e/icecast/letsencrypt/letsencrypt/cli.py", line 232, in install
    installer = display_ops.pick_installer(config, args.installer, plugins)
  File "/home/e/icecast/letsencrypt/letsencrypt/display/ops.py", line 105, in pick_installer
    config, default, plugins, question, (interfaces.IInstaller,))
  File "/home/e/icecast/letsencrypt/letsencrypt/display/ops.py", line 72, in pick_plugin
    verified.prepare()
  File "/home/e/icecast/letsencrypt/letsencrypt/plugins/disco.py", line 197, in prepare
    return [plugin_ep.prepare() for plugin_ep in self._plugins.itervalues()]
  File "/home/e/icecast/letsencrypt/letsencrypt/plugins/disco.py", line 98, in prepare
    self._initialized.prepare()
  File "/home/e/icecast/letsencrypt/icecast.py", line 151, in prepare
    self.config_file = self.conf("configuration_file")
  File "/home/e/icecast/letsencrypt/letsencrypt/plugins/common.py", line 60, in conf
    return getattr(self.config, self.dest(var))
  File "/home/e/icecast/letsencrypt/letsencrypt/configuration.py", line 39, in __getattr__
    return getattr(self.namespace, name)
AttributeError: 'Namespace' object has no attribute 'letsencrypt-icecast:icecast_installer_configuration_file'
```

I think if an argument is not specified on the command line, the argument should default to None and not throw an AttributeError. But even if I manually have default=None or set default to anything I still get this error. Curiously the attribute error is about `letsencrypt-icecast:icecast_installer_configuration_file` while when running `letsencrypt --help all` the corresponding command line argument is listed as `--letsencrypt-icecast:icecast_installer-configuration_file` (note the the dash instead of underscore near the end) which leads me to believe the conversion of the argument name in the plugin to the global name is going wrong.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `letsencrypt/plugins/common.py`
Content:
```
1 """Plugin common functions."""
2 import os
3 import pkg_resources
4 import re
5 import shutil
6 import tempfile
7 
8 import zope.interface
9 
10 from acme.jose import util as jose_util
11 
12 from letsencrypt import constants
13 from letsencrypt import interfaces
14 from letsencrypt import le_util
15 
16 
17 def option_namespace(name):
18     """ArgumentParser options namespace (prefix of all options)."""
19     return name + "-"
20 
21 def dest_namespace(name):
22     """ArgumentParser dest namespace (prefix of all destinations)."""
23     return name + "_"
24 
25 private_ips_regex = re.compile(  # pylint: disable=invalid-name
26     r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|"
27     r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)")
28 hostname_regex = re.compile(  # pylint: disable=invalid-name
29     r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE)
30 
31 
32 class Plugin(object):
33     """Generic plugin."""
34     zope.interface.implements(interfaces.IPlugin)
35     # classProvides is not inherited, subclasses must define it on their own
36     #zope.interface.classProvides(interfaces.IPluginFactory)
37 
38     def __init__(self, config, name):
39         self.config = config
40         self.name = name
41 
42     @property
43     def option_namespace(self):
44         """ArgumentParser options namespace (prefix of all options)."""
45         return option_namespace(self.name)
46 
47     @property
48     def dest_namespace(self):
49         """ArgumentParser dest namespace (prefix of all destinations)."""
50         return dest_namespace(self.name)
51 
52     def dest(self, var):
53         """Find a destination for given variable ``var``."""
54         # this should do exactly the same what ArgumentParser(arg),
55         # does to "arg" to compute "dest"
56         return self.dest_namespace + var.replace("-", "_")
57 
58     def conf(self, var):
59         """Find a configuration value for variable ``var``."""
60         return getattr(self.config, self.dest(var))
61 
62     @classmethod
63     def inject_parser_options(cls, parser, name):
64         """Inject parser options.
65 
66         See `~.IPlugin.inject_parser_options` for docs.
67 
68         """
69         # dummy function, doesn't check if dest.startswith(self.dest_namespace)
70         def add(arg_name_no_prefix, *args, **kwargs):
71             # pylint: disable=missing-docstring
72             return parser.add_argument(
73                 "--{0}{1}".format(option_namespace(name), arg_name_no_prefix),
74                 *args, **kwargs)
75         return cls.add_parser_arguments(add)
76 
77     @jose_util.abstractclassmethod
78     def add_parser_arguments(cls, add):
79         """Add plugin arguments to the CLI argument parser.
80 
81         :param callable add: Function that proxies calls to
82             `argparse.ArgumentParser.add_argument` prepending options
83             with unique plugin name prefix.
84 
85         """
86 
87 # other
88 
89 class Addr(object):
90     r"""Represents an virtual host address.
91 
92     :param str addr: addr part of vhost address
93     :param str port: port number or \*, or ""
94 
95     """
96     def __init__(self, tup):
97         self.tup = tup
98 
99     @classmethod
100     def fromstring(cls, str_addr):
101         """Initialize Addr from string."""
102         tup = str_addr.partition(':')
103         return cls((tup[0], tup[2]))
104 
105     def __str__(self):
106         if self.tup[1]:
107             return "%s:%s" % self.tup
108         return self.tup[0]
109 
110     def __eq__(self, other):
111         if isinstance(other, self.__class__):
112             return self.tup == other.tup
113         return False
114 
115     def __hash__(self):
116         return hash(self.tup)
117 
118     def get_addr(self):
119         """Return addr part of Addr object."""
120         return self.tup[0]
121 
122     def get_port(self):
123         """Return port."""
124         return self.tup[1]
125 
126     def get_addr_obj(self, port):
127         """Return new address object with same addr and new port."""
128         return self.__class__((self.tup[0], port))
129 
130 
131 class Dvsni(object):
132     """Class that perform DVSNI challenges."""
133 
134     def __init__(self, configurator):
135         self.configurator = configurator
136         self.achalls = []
137         self.indices = []
138         self.challenge_conf = os.path.join(
139             configurator.config.config_dir, "le_dvsni_cert_challenge.conf")
140         # self.completed = 0
141 
142     def add_chall(self, achall, idx=None):
143         """Add challenge to DVSNI object to perform at once.
144 
145         :param achall: Annotated DVSNI challenge.
146         :type achall: :class:`letsencrypt.achallenges.DVSNI`
147 
148         :param int idx: index to challenge in a larger array
149 
150         """
151         self.achalls.append(achall)
152         if idx is not None:
153             self.indices.append(idx)
154 
155     def get_cert_path(self, achall):
156         """Returns standardized name for challenge certificate.
157 
158         :param achall: Annotated DVSNI challenge.
159         :type achall: :class:`letsencrypt.achallenges.DVSNI`
160 
161         :returns: certificate file name
162         :rtype: str
163 
164         """
165         return os.path.join(self.configurator.config.work_dir,
166                             achall.chall.encode("token") + ".crt")
167 
168     def get_key_path(self, achall):
169         """Get standardized path to challenge key."""
170         return os.path.join(self.configurator.config.work_dir,
171                             achall.chall.encode("token") + '.pem')
172 
173     def _setup_challenge_cert(self, achall, s=None):
174         # pylint: disable=invalid-name
175         """Generate and write out challenge certificate."""
176         cert_path = self.get_cert_path(achall)
177         key_path = self.get_key_path(achall)
178         # Register the path before you write out the file
179         self.configurator.reverter.register_file_creation(True, key_path)
180         self.configurator.reverter.register_file_creation(True, cert_path)
181 
182         response, cert_pem, key_pem = achall.gen_cert_and_response(s)
183 
184         # Write out challenge cert and key
185         with open(cert_path, "wb") as cert_chall_fd:
186             cert_chall_fd.write(cert_pem)
187         with le_util.safe_open(key_path, 'wb', chmod=0o400) as key_file:
188             key_file.write(key_pem)
189 
190         return response
191 
192 
193 # test utils used by letsencrypt_apache/letsencrypt_nginx (hence
194 # "pragma: no cover") TODO: this might quickly lead to dead code (also
195 # c.f. #383)
196 
197 def setup_ssl_options(config_dir, src, dest):  # pragma: no cover
198     """Move the ssl_options into position and return the path."""
199     option_path = os.path.join(config_dir, dest)
200     shutil.copyfile(src, option_path)
201     return option_path
202 
203 
204 def dir_setup(test_dir, pkg):  # pragma: no cover
205     """Setup the directories necessary for the configurator."""
206     temp_dir = tempfile.mkdtemp("temp")
207     config_dir = tempfile.mkdtemp("config")
208     work_dir = tempfile.mkdtemp("work")
209 
210     os.chmod(temp_dir, constants.CONFIG_DIRS_MODE)
211     os.chmod(config_dir, constants.CONFIG_DIRS_MODE)
212     os.chmod(work_dir, constants.CONFIG_DIRS_MODE)
213 
214     test_configs = pkg_resources.resource_filename(
215         pkg, os.path.join("testdata", test_dir))
216 
217     shutil.copytree(
218         test_configs, os.path.join(temp_dir, test_dir), symlinks=True)
219 
220     return temp_dir, config_dir, work_dir
221
```
--- 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/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py
--- a/letsencrypt/plugins/common.py
+++ b/letsencrypt/plugins/common.py
@@ -20,7 +20,7 @@
 
 def dest_namespace(name):
     """ArgumentParser dest namespace (prefix of all destinations)."""
-    return name + "_"
+    return name.replace("-", "_") + "_"
 
 private_ips_regex = re.compile(  # pylint: disable=invalid-name
     r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|"

{"golden_diff": "diff --git a/letsencrypt/plugins/common.py b/letsencrypt/plugins/common.py\n--- a/letsencrypt/plugins/common.py\n+++ b/letsencrypt/plugins/common.py\n@@ -20,7 +20,7 @@\n \n def dest_namespace(name):\n     \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n-    return name + \"_\"\n+    return name.replace(\"-\", \"_\") + \"_\"\n \n private_ips_regex = re.compile(  # pylint: disable=invalid-name\n     r\"(^127\\.0\\.0\\.1)|(^10\\.)|(^172\\.1[6-9]\\.)|\"\n", "issue": "Command line arguments for plugins are broken\nIt seems the command line arguments for plugins created in `add_parser_arguments(cls, add):` cant be extracted with `self.conf(\"argument_name\")`. I suspect something with the plugin namespace prefix is going wrong.\n\nMy code is here https://github.com/e00E/lets-encrypt-icecast/blob/master/icecast.py .\nIn line 129 I add an argument via `add(\"configuration_file\")`\nand I try to use it in line 150 like so\n\n```\nself.config_file = self.conf(\"configuration_file\")\nif self.config_file is not None and not os.path.isfile(self.config_file):\n    raise PluginError('User supplied icecast configuration file does not exist.')\n```\n\nWhen running `letsencrypt install` it crashes with the last log message\n\n```\nTraceback (most recent call last):\n  File \"./letsencrypt/venv/bin/letsencrypt\", line 9, in <module>\n    load_entry_point('letsencrypt==0.1', 'console_scripts', 'letsencrypt')()\n  File \"/home/e/icecast/letsencrypt/letsencrypt/cli.py\", line 778, in main\n    return args.func(args, config, plugins)\n  File \"/home/e/icecast/letsencrypt/letsencrypt/cli.py\", line 232, in install\n    installer = display_ops.pick_installer(config, args.installer, plugins)\n  File \"/home/e/icecast/letsencrypt/letsencrypt/display/ops.py\", line 105, in pick_installer\n    config, default, plugins, question, (interfaces.IInstaller,))\n  File \"/home/e/icecast/letsencrypt/letsencrypt/display/ops.py\", line 72, in pick_plugin\n    verified.prepare()\n  File \"/home/e/icecast/letsencrypt/letsencrypt/plugins/disco.py\", line 197, in prepare\n    return [plugin_ep.prepare() for plugin_ep in self._plugins.itervalues()]\n  File \"/home/e/icecast/letsencrypt/letsencrypt/plugins/disco.py\", line 98, in prepare\n    self._initialized.prepare()\n  File \"/home/e/icecast/letsencrypt/icecast.py\", line 151, in prepare\n    self.config_file = self.conf(\"configuration_file\")\n  File \"/home/e/icecast/letsencrypt/letsencrypt/plugins/common.py\", line 60, in conf\n    return getattr(self.config, self.dest(var))\n  File \"/home/e/icecast/letsencrypt/letsencrypt/configuration.py\", line 39, in __getattr__\n    return getattr(self.namespace, name)\nAttributeError: 'Namespace' object has no attribute 'letsencrypt-icecast:icecast_installer_configuration_file'\n```\n\nI think if an argument is not specified on the command line, the argument should default to None and not throw an AttributeError. But even if I manually have default=None or set default to anything I still get this error. Curiously the attribute error is about `letsencrypt-icecast:icecast_installer_configuration_file` while when running `letsencrypt --help all` the corresponding command line argument is listed as `--letsencrypt-icecast:icecast_installer-configuration_file` (note the the dash instead of underscore near the end) which leads me to believe the conversion of the argument name in the plugin to the global name is going wrong.\n\n", "before_files": [{"content": "\"\"\"Plugin common functions.\"\"\"\nimport os\nimport pkg_resources\nimport re\nimport shutil\nimport tempfile\n\nimport zope.interface\n\nfrom acme.jose import util as jose_util\n\nfrom letsencrypt import constants\nfrom letsencrypt import interfaces\nfrom letsencrypt import le_util\n\n\ndef option_namespace(name):\n    \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n    return name + \"-\"\n\ndef dest_namespace(name):\n    \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n    return name + \"_\"\n\nprivate_ips_regex = re.compile(  # pylint: disable=invalid-name\n    r\"(^127\\.0\\.0\\.1)|(^10\\.)|(^172\\.1[6-9]\\.)|\"\n    r\"(^172\\.2[0-9]\\.)|(^172\\.3[0-1]\\.)|(^192\\.168\\.)\")\nhostname_regex = re.compile(  # pylint: disable=invalid-name\n    r\"^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*[a-z]+$\", re.IGNORECASE)\n\n\nclass Plugin(object):\n    \"\"\"Generic plugin.\"\"\"\n    zope.interface.implements(interfaces.IPlugin)\n    # classProvides is not inherited, subclasses must define it on their own\n    #zope.interface.classProvides(interfaces.IPluginFactory)\n\n    def __init__(self, config, name):\n        self.config = config\n        self.name = name\n\n    @property\n    def option_namespace(self):\n        \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n        return option_namespace(self.name)\n\n    @property\n    def dest_namespace(self):\n        \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n        return dest_namespace(self.name)\n\n    def dest(self, var):\n        \"\"\"Find a destination for given variable ``var``.\"\"\"\n        # this should do exactly the same what ArgumentParser(arg),\n        # does to \"arg\" to compute \"dest\"\n        return self.dest_namespace + var.replace(\"-\", \"_\")\n\n    def conf(self, var):\n        \"\"\"Find a configuration value for variable ``var``.\"\"\"\n        return getattr(self.config, self.dest(var))\n\n    @classmethod\n    def inject_parser_options(cls, parser, name):\n        \"\"\"Inject parser options.\n\n        See `~.IPlugin.inject_parser_options` for docs.\n\n        \"\"\"\n        # dummy function, doesn't check if dest.startswith(self.dest_namespace)\n        def add(arg_name_no_prefix, *args, **kwargs):\n            # pylint: disable=missing-docstring\n            return parser.add_argument(\n                \"--{0}{1}\".format(option_namespace(name), arg_name_no_prefix),\n                *args, **kwargs)\n        return cls.add_parser_arguments(add)\n\n    @jose_util.abstractclassmethod\n    def add_parser_arguments(cls, add):\n        \"\"\"Add plugin arguments to the CLI argument parser.\n\n        :param callable add: Function that proxies calls to\n            `argparse.ArgumentParser.add_argument` prepending options\n            with unique plugin name prefix.\n\n        \"\"\"\n\n# other\n\nclass Addr(object):\n    r\"\"\"Represents an virtual host address.\n\n    :param str addr: addr part of vhost address\n    :param str port: port number or \\*, or \"\"\n\n    \"\"\"\n    def __init__(self, tup):\n        self.tup = tup\n\n    @classmethod\n    def fromstring(cls, str_addr):\n        \"\"\"Initialize Addr from string.\"\"\"\n        tup = str_addr.partition(':')\n        return cls((tup[0], tup[2]))\n\n    def __str__(self):\n        if self.tup[1]:\n            return \"%s:%s\" % self.tup\n        return self.tup[0]\n\n    def __eq__(self, other):\n        if isinstance(other, self.__class__):\n            return self.tup == other.tup\n        return False\n\n    def __hash__(self):\n        return hash(self.tup)\n\n    def get_addr(self):\n        \"\"\"Return addr part of Addr object.\"\"\"\n        return self.tup[0]\n\n    def get_port(self):\n        \"\"\"Return port.\"\"\"\n        return self.tup[1]\n\n    def get_addr_obj(self, port):\n        \"\"\"Return new address object with same addr and new port.\"\"\"\n        return self.__class__((self.tup[0], port))\n\n\nclass Dvsni(object):\n    \"\"\"Class that perform DVSNI challenges.\"\"\"\n\n    def __init__(self, configurator):\n        self.configurator = configurator\n        self.achalls = []\n        self.indices = []\n        self.challenge_conf = os.path.join(\n            configurator.config.config_dir, \"le_dvsni_cert_challenge.conf\")\n        # self.completed = 0\n\n    def add_chall(self, achall, idx=None):\n        \"\"\"Add challenge to DVSNI object to perform at once.\n\n        :param achall: Annotated DVSNI challenge.\n        :type achall: :class:`letsencrypt.achallenges.DVSNI`\n\n        :param int idx: index to challenge in a larger array\n\n        \"\"\"\n        self.achalls.append(achall)\n        if idx is not None:\n            self.indices.append(idx)\n\n    def get_cert_path(self, achall):\n        \"\"\"Returns standardized name for challenge certificate.\n\n        :param achall: Annotated DVSNI challenge.\n        :type achall: :class:`letsencrypt.achallenges.DVSNI`\n\n        :returns: certificate file name\n        :rtype: str\n\n        \"\"\"\n        return os.path.join(self.configurator.config.work_dir,\n                            achall.chall.encode(\"token\") + \".crt\")\n\n    def get_key_path(self, achall):\n        \"\"\"Get standardized path to challenge key.\"\"\"\n        return os.path.join(self.configurator.config.work_dir,\n                            achall.chall.encode(\"token\") + '.pem')\n\n    def _setup_challenge_cert(self, achall, s=None):\n        # pylint: disable=invalid-name\n        \"\"\"Generate and write out challenge certificate.\"\"\"\n        cert_path = self.get_cert_path(achall)\n        key_path = self.get_key_path(achall)\n        # Register the path before you write out the file\n        self.configurator.reverter.register_file_creation(True, key_path)\n        self.configurator.reverter.register_file_creation(True, cert_path)\n\n        response, cert_pem, key_pem = achall.gen_cert_and_response(s)\n\n        # Write out challenge cert and key\n        with open(cert_path, \"wb\") as cert_chall_fd:\n            cert_chall_fd.write(cert_pem)\n        with le_util.safe_open(key_path, 'wb', chmod=0o400) as key_file:\n            key_file.write(key_pem)\n\n        return response\n\n\n# test utils used by letsencrypt_apache/letsencrypt_nginx (hence\n# \"pragma: no cover\") TODO: this might quickly lead to dead code (also\n# c.f. #383)\n\ndef setup_ssl_options(config_dir, src, dest):  # pragma: no cover\n    \"\"\"Move the ssl_options into position and return the path.\"\"\"\n    option_path = os.path.join(config_dir, dest)\n    shutil.copyfile(src, option_path)\n    return option_path\n\n\ndef dir_setup(test_dir, pkg):  # pragma: no cover\n    \"\"\"Setup the directories necessary for the configurator.\"\"\"\n    temp_dir = tempfile.mkdtemp(\"temp\")\n    config_dir = tempfile.mkdtemp(\"config\")\n    work_dir = tempfile.mkdtemp(\"work\")\n\n    os.chmod(temp_dir, constants.CONFIG_DIRS_MODE)\n    os.chmod(config_dir, constants.CONFIG_DIRS_MODE)\n    os.chmod(work_dir, constants.CONFIG_DIRS_MODE)\n\n    test_configs = pkg_resources.resource_filename(\n        pkg, os.path.join(\"testdata\", test_dir))\n\n    shutil.copytree(\n        test_configs, os.path.join(temp_dir, test_dir), symlinks=True)\n\n    return temp_dir, config_dir, work_dir\n", "path": "letsencrypt/plugins/common.py"}], "after_files": [{"content": "\"\"\"Plugin common functions.\"\"\"\nimport os\nimport pkg_resources\nimport re\nimport shutil\nimport tempfile\n\nimport zope.interface\n\nfrom acme.jose import util as jose_util\n\nfrom letsencrypt import constants\nfrom letsencrypt import interfaces\nfrom letsencrypt import le_util\n\n\ndef option_namespace(name):\n    \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n    return name + \"-\"\n\ndef dest_namespace(name):\n    \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n    return name.replace(\"-\", \"_\") + \"_\"\n\nprivate_ips_regex = re.compile(  # pylint: disable=invalid-name\n    r\"(^127\\.0\\.0\\.1)|(^10\\.)|(^172\\.1[6-9]\\.)|\"\n    r\"(^172\\.2[0-9]\\.)|(^172\\.3[0-1]\\.)|(^192\\.168\\.)\")\nhostname_regex = re.compile(  # pylint: disable=invalid-name\n    r\"^(([a-z0-9]|[a-z0-9][a-z0-9\\-]*[a-z0-9])\\.)*[a-z]+$\", re.IGNORECASE)\n\n\nclass Plugin(object):\n    \"\"\"Generic plugin.\"\"\"\n    zope.interface.implements(interfaces.IPlugin)\n    # classProvides is not inherited, subclasses must define it on their own\n    #zope.interface.classProvides(interfaces.IPluginFactory)\n\n    def __init__(self, config, name):\n        self.config = config\n        self.name = name\n\n    @property\n    def option_namespace(self):\n        \"\"\"ArgumentParser options namespace (prefix of all options).\"\"\"\n        return option_namespace(self.name)\n\n    @property\n    def dest_namespace(self):\n        \"\"\"ArgumentParser dest namespace (prefix of all destinations).\"\"\"\n        return dest_namespace(self.name)\n\n    def dest(self, var):\n        \"\"\"Find a destination for given variable ``var``.\"\"\"\n        # this should do exactly the same what ArgumentParser(arg),\n        # does to \"arg\" to compute \"dest\"\n        return self.dest_namespace + var.replace(\"-\", \"_\")\n\n    def conf(self, var):\n        \"\"\"Find a configuration value for variable ``var``.\"\"\"\n        return getattr(self.config, self.dest(var))\n\n    @classmethod\n    def inject_parser_options(cls, parser, name):\n        \"\"\"Inject parser options.\n\n        See `~.IPlugin.inject_parser_options` for docs.\n\n        \"\"\"\n        # dummy function, doesn't check if dest.startswith(self.dest_namespace)\n        def add(arg_name_no_prefix, *args, **kwargs):\n            # pylint: disable=missing-docstring\n            return parser.add_argument(\n                \"--{0}{1}\".format(option_namespace(name), arg_name_no_prefix),\n                *args, **kwargs)\n        return cls.add_parser_arguments(add)\n\n    @jose_util.abstractclassmethod\n    def add_parser_arguments(cls, add):\n        \"\"\"Add plugin arguments to the CLI argument parser.\n\n        :param callable add: Function that proxies calls to\n            `argparse.ArgumentParser.add_argument` prepending options\n            with unique plugin name prefix.\n\n        \"\"\"\n\n# other\n\nclass Addr(object):\n    r\"\"\"Represents an virtual host address.\n\n    :param str addr: addr part of vhost address\n    :param str port: port number or \\*, or \"\"\n\n    \"\"\"\n    def __init__(self, tup):\n        self.tup = tup\n\n    @classmethod\n    def fromstring(cls, str_addr):\n        \"\"\"Initialize Addr from string.\"\"\"\n        tup = str_addr.partition(':')\n        return cls((tup[0], tup[2]))\n\n    def __str__(self):\n        if self.tup[1]:\n            return \"%s:%s\" % self.tup\n        return self.tup[0]\n\n    def __eq__(self, other):\n        if isinstance(other, self.__class__):\n            return self.tup == other.tup\n        return False\n\n    def __hash__(self):\n        return hash(self.tup)\n\n    def get_addr(self):\n        \"\"\"Return addr part of Addr object.\"\"\"\n        return self.tup[0]\n\n    def get_port(self):\n        \"\"\"Return port.\"\"\"\n        return self.tup[1]\n\n    def get_addr_obj(self, port):\n        \"\"\"Return new address object with same addr and new port.\"\"\"\n        return self.__class__((self.tup[0], port))\n\n\nclass Dvsni(object):\n    \"\"\"Class that perform DVSNI challenges.\"\"\"\n\n    def __init__(self, configurator):\n        self.configurator = configurator\n        self.achalls = []\n        self.indices = []\n        self.challenge_conf = os.path.join(\n            configurator.config.config_dir, \"le_dvsni_cert_challenge.conf\")\n        # self.completed = 0\n\n    def add_chall(self, achall, idx=None):\n        \"\"\"Add challenge to DVSNI object to perform at once.\n\n        :param achall: Annotated DVSNI challenge.\n        :type achall: :class:`letsencrypt.achallenges.DVSNI`\n\n        :param int idx: index to challenge in a larger array\n\n        \"\"\"\n        self.achalls.append(achall)\n        if idx is not None:\n            self.indices.append(idx)\n\n    def get_cert_path(self, achall):\n        \"\"\"Returns standardized name for challenge certificate.\n\n        :param achall: Annotated DVSNI challenge.\n        :type achall: :class:`letsencrypt.achallenges.DVSNI`\n\n        :returns: certificate file name\n        :rtype: str\n\n        \"\"\"\n        return os.path.join(self.configurator.config.work_dir,\n                            achall.chall.encode(\"token\") + \".crt\")\n\n    def get_key_path(self, achall):\n        \"\"\"Get standardized path to challenge key.\"\"\"\n        return os.path.join(self.configurator.config.work_dir,\n                            achall.chall.encode(\"token\") + '.pem')\n\n    def _setup_challenge_cert(self, achall, s=None):\n        # pylint: disable=invalid-name\n        \"\"\"Generate and write out challenge certificate.\"\"\"\n        cert_path = self.get_cert_path(achall)\n        key_path = self.get_key_path(achall)\n        # Register the path before you write out the file\n        self.configurator.reverter.register_file_creation(True, key_path)\n        self.configurator.reverter.register_file_creation(True, cert_path)\n\n        response, cert_pem, key_pem = achall.gen_cert_and_response(s)\n\n        # Write out challenge cert and key\n        with open(cert_path, \"wb\") as cert_chall_fd:\n            cert_chall_fd.write(cert_pem)\n        with le_util.safe_open(key_path, 'wb', chmod=0o400) as key_file:\n            key_file.write(key_pem)\n\n        return response\n\n\n# test utils used by letsencrypt_apache/letsencrypt_nginx (hence\n# \"pragma: no cover\") TODO: this might quickly lead to dead code (also\n# c.f. #383)\n\ndef setup_ssl_options(config_dir, src, dest):  # pragma: no cover\n    \"\"\"Move the ssl_options into position and return the path.\"\"\"\n    option_path = os.path.join(config_dir, dest)\n    shutil.copyfile(src, option_path)\n    return option_path\n\n\ndef dir_setup(test_dir, pkg):  # pragma: no cover\n    \"\"\"Setup the directories necessary for the configurator.\"\"\"\n    temp_dir = tempfile.mkdtemp(\"temp\")\n    config_dir = tempfile.mkdtemp(\"config\")\n    work_dir = tempfile.mkdtemp(\"work\")\n\n    os.chmod(temp_dir, constants.CONFIG_DIRS_MODE)\n    os.chmod(config_dir, constants.CONFIG_DIRS_MODE)\n    os.chmod(work_dir, constants.CONFIG_DIRS_MODE)\n\n    test_configs = pkg_resources.resource_filename(\n        pkg, os.path.join(\"testdata\", test_dir))\n\n    shutil.copytree(\n        test_configs, os.path.join(temp_dir, test_dir), symlinks=True)\n\n    return temp_dir, config_dir, work_dir\n", "path": "letsencrypt/plugins/common.py"}]}