1# alias.py
2# Alias CLI command.
3#
4# Copyright (C) 2018 Red Hat, Inc.
5#
6# This copyrighted material is made available to anyone wishing to use,
7# modify, copy, or redistribute it subject to the terms and conditions of
8# the GNU General Public License v.2, or (at your option) any later version.
9# This program is distributed in the hope that it will be useful, but WITHOUT
10# ANY WARRANTY expressed or implied, including the implied warranties of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
12# Public License for more details.  You should have received a copy of the
13# GNU General Public License along with this program; if not, write to the
14# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
16# source code or documentation are not subject to the GNU General Public
17# License and may only be used or replicated with the express permission of
18# Red Hat, Inc.
19#
20
21from __future__ import absolute_import
22from __future__ import print_function
23from __future__ import unicode_literals
24
25import logging
26import os.path
27
28import dnf.cli
29import dnf.cli.aliases
30from dnf.cli import commands
31import dnf.conf
32import dnf.exceptions
33from dnf.i18n import _
34
35logger = logging.getLogger('dnf')
36
37
38class AliasCommand(commands.Command):
39    aliases = ('alias',)
40    summary = _('List or create command aliases')
41
42    @staticmethod
43    def set_argparser(parser):
44        enable_group = parser.add_mutually_exclusive_group()
45        enable_group.add_argument(
46            '--enable-resolving', default=False, action='store_true',
47            help=_('enable aliases resolving'))
48        enable_group.add_argument(
49            '--disable-resolving', default=False, action='store_true',
50            help=_('disable aliases resolving'))
51        parser.add_argument("subcommand", nargs='?', default='list',
52                            choices=['add', 'list', 'delete'],
53                            help=_("action to do with aliases"))
54        parser.add_argument("alias", nargs="*", metavar="command[=result]",
55                            help=_("alias definition"))
56
57    def configure(self):
58        demands = self.cli.demands
59        if self.opts.subcommand in ('add', 'delete'):
60            demands.root_user = True
61        self.aliases_base = dnf.cli.aliases.Aliases()
62        self.aliases_base._load_aliases()
63        self.resolving_enabled = self.aliases_base.enabled
64        self._update_config_from_options()
65
66    def _update_config_from_options(self):
67        enabled = None
68        if self.opts.enable_resolving:
69            enabled = True
70            logger.info(_("Aliases are now enabled"))
71        if self.opts.disable_resolving:
72            enabled = False
73            logger.info(_("Aliases are now disabled"))
74
75        if enabled is not None:
76            if not os.path.exists(dnf.cli.aliases.ALIASES_CONF_PATH):
77                open(dnf.cli.aliases.ALIASES_CONF_PATH, 'w').close()
78            dnf.conf.BaseConfig.write_raw_configfile(
79                dnf.cli.aliases.ALIASES_CONF_PATH,
80                'main', None, {'enabled': enabled})
81            if not self.aliases_base._disabled_by_environ():
82                self.aliases_base.enabled = enabled
83
84    def _parse_option_alias(self):
85        new_aliases = {}
86        for alias in self.opts.alias:
87            alias = alias.split('=', 1)
88            cmd = alias[0].strip()
89            if len(cmd.split()) != 1:
90                logger.warning(_("Invalid alias key: %s"), cmd)
91                continue
92            if cmd.startswith('-'):
93                logger.warning(_("Invalid alias key: %s"), cmd)
94                continue
95            if len(alias) == 1:
96                logger.warning(_("Alias argument has no value: %s"), cmd)
97                continue
98            new_aliases[cmd] = alias[1].split()
99        return new_aliases
100
101    def _load_user_aliases(self):
102        if not os.path.exists(dnf.cli.aliases.ALIASES_USER_PATH):
103            open(dnf.cli.aliases.ALIASES_USER_PATH, 'w').close()
104        try:
105            conf = dnf.cli.aliases.AliasesConfig(
106                dnf.cli.aliases.ALIASES_USER_PATH)
107        except dnf.exceptions.ConfigError as e:
108            logger.warning(_('Config error: %s'), e)
109            return None
110        return conf
111
112    def _store_user_aliases(self, user_aliases, enabled):
113        fileobj = open(dnf.cli.aliases.ALIASES_USER_PATH, 'w')
114        output = "[main]\n"
115        output += "enabled = {}\n\n".format(enabled)
116        output += "[aliases]\n"
117        for key, value in user_aliases.items():
118            output += "{} = {}\n".format(key, ' '.join(value))
119        fileobj.write(output)
120
121    def add_aliases(self, aliases):
122        conf = self._load_user_aliases()
123        user_aliases = conf.aliases
124        if user_aliases is None:
125            return
126
127        user_aliases.update(aliases)
128
129        self._store_user_aliases(user_aliases, conf.enabled)
130        logger.info(_("Aliases added: %s"), ', '.join(aliases.keys()))
131
132    def remove_aliases(self, cmds):
133        conf = self._load_user_aliases()
134        user_aliases = conf.aliases
135        if user_aliases is None:
136            return
137
138        valid_cmds = []
139        for cmd in cmds:
140            try:
141                del user_aliases[cmd]
142                valid_cmds.append(cmd)
143            except KeyError:
144                logger.info(_("Alias not found: %s"), cmd)
145
146        self._store_user_aliases(user_aliases, conf.enabled)
147        logger.info(_("Aliases deleted: %s"), ', '.join(valid_cmds))
148
149    def list_alias(self, cmd):
150        args = [cmd]
151        try:
152            args = self.aliases_base._resolve(args)
153        except dnf.exceptions.Error as e:
154            logger.error(
155                _('%s, alias %s="%s"'), e, cmd, (' ').join(self.aliases_base.aliases[cmd]))
156        else:
157            print(_("Alias %s='%s'") % (cmd, " ".join(args)))
158
159    def run(self):
160        if not self.aliases_base.enabled:
161            logger.warning(_("Aliases resolving is disabled."))
162
163        if self.opts.subcommand == 'add':  # Add new alias
164            aliases = self._parse_option_alias()
165            if not aliases:
166                raise dnf.exceptions.Error(_("No aliases specified."))
167            self.add_aliases(aliases)
168            return
169
170        if self.opts.subcommand == 'delete':  # Remove alias
171            cmds = self.opts.alias
172            if cmds == []:
173                raise dnf.exceptions.Error(_("No alias specified."))
174            self.remove_aliases(cmds)
175            return
176
177        if not self.opts.alias:  # List all aliases
178            if not self.aliases_base.aliases:
179                logger.info(_("No aliases defined."))
180                return
181            for cmd in self.aliases_base.aliases:
182                self.list_alias(cmd)
183        else:  # List alias by key
184            for cmd in self.opts.alias:
185                if cmd not in self.aliases_base.aliases:
186                    logger.info(_("No match for alias: %s") % cmd)
187                    continue
188                self.list_alias(cmd)
189