Commit 869120d9 authored by adam j hartz's avatar adam j hartz

Merge branch 'master' of

parents 149170bb 46df2825
......@@ -145,7 +145,7 @@ def main():
'Topic :: System :: Shells',
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)'],
packages=['takoshell', 'takoshell.ply', 'takoshell.parsers',
'takoshell.xoreutils', 'takoshell.completers'],
'takoshell.coreutils', 'takoshell.completers'],
package_dir={'takoshell': 'takoshell'},
package_data={'takoshell': ['*.json', 'LICENSE']},
......@@ -19,4 +19,4 @@
# xonsh is Copyright (c) 2015-2016 the xonsh developers and is licensed under
# the 2-Clause BSD license.
__version__ = '0.2.2'
__version__ = '0.2.3'
......@@ -36,7 +36,7 @@ from argparse import ArgumentParser
from takoshell.dirstack import cd, pushd, popd, dirs
from import jobs, fg, bg, clean_jobs, disown
from takoshell.xoreutils import _which
from takoshell.coreutils import _which, _echo, _umask
from takoshell.completers._aliases import completer_alias
......@@ -373,4 +373,6 @@ default_aliases = {
'fgrep': ['fgrep', '--color=auto'],
'ls': ['ls', '--color=auto', '-v'],
'suppress_tako_welcome_message': suppress_welcome,
'echo': _echo.echo,
'umask': _umask.umask,
......@@ -13,9 +13,46 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <>.
# tako is a fork of xonsh (
# xonsh is Copyright (c) 2015-2016 the xonsh developers and is licensed under
# the 2-Clause BSD license.
"""Implements a simple echo command for tako."""
def echo(args, stdin, stdout, stderr):
"""A simple echo command."""
opts = _echo_parse_args(args)
if opts is None:
if opts['help']:
print(ECHO_HELP, file=stdout)
return 0
ender = opts['end']
args = map(str, args)
if opts['escapes']:
args = map(lambda x: x.encode().decode('unicode_escape'), args)
print(*args, end=ender, file=stdout)
def _echo_parse_args(args):
out = {'escapes': False, 'end': '\n', 'help': False}
if '-e' in args:
out['escapes'] = True
if '-E' in args:
out['escapes'] = False
if '-n' in args:
out['end'] = ''
if '-h' in args or '--help' in args:
out['help'] = True
return out
ECHO_HELP = """Usage: echo [OPTIONS]... [STRING]...
Echo the STRING(s) to standard output.
-n do not include the trailing newline
-e enable interpretation of backslash escapes
-E disable interpretation of backslash escapes (default)
-h --help display this message and exit
This version of echo was written in Python for tako:
Based on echo from GNU coreutils:"""
# This file is part of tako
# Copyright (c) 2015-2017 Adam Hartz <> and contributors
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <>.
"""Implements a umask command for tako."""
import re
import os
symbolic_matcher = re.compile(r'([ugo]*|a)([+-=])([^\s,]*)')
order = 'rwx'
name_to_value = {'x': 1, 'w': 2, 'r': 4}
value_to_name = {v: k for k, v in name_to_value.items()}
class_to_loc = {'u': 6, 'g': 3, 'o': 0} # how many bits to shift this class by
loc_to_class = {v: k for k, v in class_to_loc.items()}
function_map = {
'+': lambda orig, new: orig | new, # add the given permission
'-': lambda orig, new: orig & ~new, # remove the given permission
'=': lambda orig, new: new, # set the permissions exactly
def current_mask():
out = os.umask(0)
return out
def invert(perms):
return 0o777 - perms
def get_oct_digits(mode):
Separate a given integer into its three components
if not 0 <= mode <= 0o777:
raise ValueError("expected a value between 000 and 777")
return {'u': (mode & 0o700) >> 6,
'g': (mode & 0o070) >> 3,
'o': mode & 0o007}
def from_oct_digits(digits):
o = 0
for c, m in digits.items():
o |= (m << class_to_loc[c])
return o
def get_symbolic_rep_single(digit):
Given a single octal digit, return the appropriate string representation.
For example, 6 becomes "rw".
o = ''
for sym in 'rwx':
num = name_to_value[sym]
if digit & num:
o += sym
digit -= num
return o
def get_symbolic_rep(number):
digits = get_oct_digits(number)
return ','.join('%s=%s' % (class_, get_symbolic_rep_single(digits[class_]))
for class_ in 'ugo')
def get_numeric_rep_single(rep):
Given a string representation, return the appropriate octal digit.
For example, "rw" becomes 6.
o = 0
for sym in set(rep):
o += name_to_value[sym]
return o
def single_symbolic_arg(arg, old=None):
# we'll assume this always operates in the "forward" direction (on the
# current permissions) rather than on the mask directly.
if old is None:
old = invert(current_mask())
match = symbolic_matcher.match(arg)
if not match:
raise ValueError('could not parse argument %r' % arg)
class_, op, mask = match.groups()
if class_ == 'a':
class_ = 'ugo'
invalid_chars = [i for i in mask if i not in name_to_value]
if invalid_chars:
raise ValueError('invalid mask %r' % mask)
digits = get_oct_digits(old)
new_num = get_numeric_rep_single(mask)
for c in set(class_):
digits[c] = function_map[op](digits[c], new_num)
return from_oct_digits(digits)
def valid_numeric_argument(x):
return len(x) == 3 and all(0 <= int(i) <= 7 for i in x)
return False
def umask(args, stdin, stdout, stderr):
if '-h' in args:
print(UMASK_HELP, file=stdout)
return 0
symbolic = False
while '-S' in args:
symbolic = True
cur = current_mask()
if len(args) == 0:
# just print the current mask
if symbolic:
to_print = get_symbolic_rep(invert(cur))
to_print = oct(cur)[2:]
while len(to_print) < 3:
to_print = '0%s' % to_print
print(to_print, file=stdout)
return 0
num = [valid_numeric_argument(i) for i in args]
if any(num):
if not all(num):
print("error: can't mix numeric and symbolic arguments", file=stderr)
return 1
if len(num) != 1:
print("error: can't have more than one numeric argument", file=stderr)
return 1
for arg, isnum in zip(args, num):
if isnum:
cur = int(arg, 8)
# this mode operates not on the mask, but on the current
# _permissions_. so invert first, operate, then invert back.
cur = invert(cur)
for subarg in arg.split(','):
cur = single_symbolic_arg(subarg, cur)
print('error: could not parse argument: %r' % subarg, file=stderr)
return 1
cur = invert(cur)
UMASK_HELP = """Usage: umask [-S] [mode]...
View or set the file creation mask.
-S when printing, show output in symbolic format
-h --help display this message and exit
This version of umask was written in Python for tako:
Based on the umask command from Bash:"""
......@@ -303,7 +303,7 @@ class Env(MutableMapping):
args = self._d['ARGS']
ix = int(
if ix >= len(args):
e = "Not enough arguments given to access ARG{0}."
e = "Not enough arguments given to access ${0}."
raise KeyError(e.format(ix))
val = self._d['ARGS'][ix]
elif key in self._d:
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment