"""Implements a umask command for xonsh."""importosimportreimportxonsh.lib.lazyasdasxl@xl.lazyobjectdefsymbolic_matcher():returnre.compile(r"([ugo]*|a)([+-=])([^\s,]*)")order="rwx"name_to_value={"x":1,"w":2,"r":4}value_to_name={v:kfork,vinname_to_value.items()}class_to_loc={"u":6,"g":3,"o":0}# how many bits to shift this class byloc_to_class={v:kfork,vinclass_to_loc.items()}function_map={"+":lambdaorig,new:orig|new,# add the given permission"-":lambdaorig,new:orig&~new,# remove the given permission"=":lambdaorig,new:new,# set the permissions exactly}
[docs]defget_oct_digits(mode):""" Separate a given integer into its three components """ifnot0<=mode<=0o777:raiseValueError("expected a value between 000 and 777")return{"u":(mode&0o700)>>6,"g":(mode&0o070)>>3,"o":mode&0o007}
[docs]defget_symbolic_rep_single(digit):""" Given a single octal digit, return the appropriate string representation. For example, 6 becomes "rw". """o=""forsymin"rwx":num=name_to_value[sym]ifdigit&num:o+=symdigit-=numreturno
[docs]defget_numeric_rep_single(rep):""" Given a string representation, return the appropriate octal digit. For example, "rw" becomes 6. """o=0forsyminset(rep):o+=name_to_value[sym]returno
[docs]defsingle_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.ifoldisNone:old=invert(current_mask())match=symbolic_matcher.match(arg)ifnotmatch:raiseValueError(f"could not parse argument {arg!r}")class_,op,mask=match.groups()ifclass_=="a":class_="ugo"invalid_chars=[iforiinmaskifinotinname_to_value]ifinvalid_chars:raiseValueError(f"invalid mask {mask!r}")digits=get_oct_digits(old)new_num=get_numeric_rep_single(mask)forcinset(class_):digits[c]=function_map[op](digits[c],new_num)returnfrom_oct_digits(digits)
[docs]defumask(args,stdin,stdout,stderr):if"-h"inargs:print(UMASK_HELP,file=stdout)return0symbolic=Falsewhile"-S"inargs:symbolic=Trueargs.remove("-S")cur=current_mask()iflen(args)==0:# just print the current maskifsymbolic:to_print=get_symbolic_rep(invert(cur))else:to_print=oct(cur)[2:]whilelen(to_print)<3:to_print=f"0{to_print}"print(to_print,file=stdout)return0else:num=[valid_numeric_argument(i)foriinargs]ifany(num):ifnotall(num):print("error: can't mix numeric and symbolic arguments",file=stderr)return1iflen(num)!=1:print("error: can't have more than one numeric argument",file=stderr)return1forarg,isnuminzip(args,num):ifisnum:cur=int(arg,8)else:# this mode operates not on the mask, but on the current# _permissions_. so invert first, operate, then invert back.cur=invert(cur)forsubarginarg.split(","):try:cur=single_symbolic_arg(subarg,cur)except:print(f"error: could not parse argument: {subarg!r}",file=stderr)return1cur=invert(cur)os.umask(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 exitThis version of umask was written in Python for tako: https://takoshell.orgBased on the umask command from Bash:https://www.gnu.org/software/bash/manual/html_node/Bourne-Shell-Builtins.html"""if__name__=="__main__":importsysumask(sys.argv,sys.stdin,sys.stdout,sys.stderr)