github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/ls_colors.go (about)

     1  package edit
     2  
     3  import (
     4  	"os"
     5  	"path"
     6  	"strings"
     7  	"syscall"
     8  )
     9  
    10  // Color files based on their various features.
    11  //
    12  // This is a reverse-engineered implementation of the parsing and
    13  // interpretation of the LS_COLORS environmental variable used by GNU
    14  // coreutils.
    15  
    16  type fileFeature int
    17  
    18  const (
    19  	featureInvalid fileFeature = iota
    20  
    21  	featureOrphanedSymlink
    22  	featureSymlink
    23  
    24  	featureMultiHardLink
    25  
    26  	featureNamedPipe
    27  	featureSocket
    28  	featureDoor
    29  	featureBlockDevice
    30  	featureCharDevice
    31  
    32  	featureWorldWritableStickyDirectory
    33  	featureWorldWritableDirectory
    34  	featureStickyDirectory
    35  	featureDirectory
    36  
    37  	featureCapability
    38  
    39  	featureSetuid
    40  	featureSetgid
    41  	featureExecutable
    42  
    43  	featureRegular
    44  )
    45  
    46  var featureForName = map[string]fileFeature{
    47  	"rs": featureRegular,
    48  	"di": featureDirectory,
    49  	"ln": featureSymlink,
    50  	"mh": featureMultiHardLink,
    51  	"pi": featureNamedPipe,
    52  	"so": featureSocket,
    53  	"do": featureDoor,
    54  	"bd": featureBlockDevice,
    55  	"cd": featureCharDevice,
    56  	"or": featureOrphanedSymlink,
    57  	"su": featureSetuid,
    58  	"sg": featureSetgid,
    59  	"ca": featureCapability,
    60  	"tw": featureWorldWritableStickyDirectory,
    61  	"ow": featureWorldWritableDirectory,
    62  	"st": featureStickyDirectory,
    63  	"ex": featureExecutable,
    64  }
    65  
    66  type lsColor struct {
    67  	styleForFeature map[fileFeature]string
    68  	styleForExt     map[string]string
    69  }
    70  
    71  const defaultLsColorString = `rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:`
    72  
    73  var defaultLsColor *lsColor
    74  
    75  // parseLsColor parses a string in the LS_COLORS format into lsColor. Erroneous
    76  // fields are silently ignored.
    77  func parseLsColor(s string) *lsColor {
    78  	lc := &lsColor{make(map[fileFeature]string), make(map[string]string)}
    79  	for _, spec := range strings.Split(s, ":") {
    80  		words := strings.Split(spec, "=")
    81  		if len(words) != 2 {
    82  			continue
    83  		}
    84  		key, value := words[0], words[1]
    85  		if strings.HasPrefix(key, "*.") {
    86  			lc.styleForExt[key[2:]] = value
    87  		} else {
    88  			feature, ok := featureForName[key]
    89  			if !ok {
    90  				continue
    91  			}
    92  			lc.styleForFeature[feature] = value
    93  		}
    94  	}
    95  	return lc
    96  }
    97  
    98  func is(u, p uint32) bool {
    99  	return u&p == p
   100  }
   101  
   102  // Weirdly, permission masks for group and other are missing on platforms other
   103  // than linux, darwin and netbsd. So we replicate some of them here.
   104  const (
   105  	S_IWOTH = 0x2 // Writable by other
   106  	S_IXGRP = 0x8 // Executable by group
   107  	S_IXOTH = 0x1 // Executable by other
   108  )
   109  
   110  func determineFeature(fname string, mh bool) (fileFeature, error) {
   111  	var stat syscall.Stat_t
   112  	err := syscall.Lstat(fname, &stat)
   113  	if err != nil {
   114  		return 0, err
   115  	}
   116  
   117  	// The type of syscall.Stat_t.Mode is uint32 on Linux and uint16 on Mac
   118  	m := (uint32)(stat.Mode)
   119  
   120  	// Symlink and OrphanedSymlink has highest precedence
   121  	if is(m, syscall.S_IFLNK) {
   122  		_, err := os.Stat(fname)
   123  		if err != nil {
   124  			return featureOrphanedSymlink, nil
   125  		}
   126  		return featureSymlink, nil
   127  	}
   128  
   129  	// featureMultiHardLink
   130  	if mh && stat.Nlink > 1 {
   131  		return featureMultiHardLink, nil
   132  	}
   133  
   134  	// type bits features
   135  	switch {
   136  	case is(m, syscall.S_IFIFO):
   137  		return featureNamedPipe, nil
   138  	case is(m, syscall.S_IFSOCK):
   139  		return featureSocket, nil
   140  		/*
   141  			case m | syscall.S_IFDOOR != 0:
   142  				return featureDoor, nil
   143  		*/
   144  	case is(m, syscall.S_IFBLK):
   145  		return featureBlockDevice, nil
   146  	case is(m, syscall.S_IFCHR):
   147  		return featureCharDevice, nil
   148  	case is(m, syscall.S_IFDIR):
   149  		// Perm bits features for directory
   150  		switch {
   151  		case is(m, S_IWOTH|syscall.S_ISVTX):
   152  			return featureWorldWritableStickyDirectory, nil
   153  		case is(m, S_IWOTH):
   154  			return featureWorldWritableDirectory, nil
   155  		case is(m, syscall.S_ISVTX):
   156  			return featureStickyDirectory, nil
   157  		default:
   158  			return featureDirectory, nil
   159  		}
   160  	}
   161  
   162  	// TODO(xiaq): Support featureCapacity
   163  
   164  	// Perm bits features for regular files
   165  	switch {
   166  	case is(m, syscall.S_ISUID):
   167  		return featureSetuid, nil
   168  	case is(m, syscall.S_ISGID):
   169  		return featureSetgid, nil
   170  	case m&(syscall.S_IXUSR|S_IXGRP|S_IXOTH) != 0:
   171  		return featureExecutable, nil
   172  	}
   173  
   174  	// Check extension
   175  	return featureRegular, nil
   176  }
   177  
   178  func (lc *lsColor) getStyle(fname string) string {
   179  	mh := strings.Trim(lc.styleForFeature[featureMultiHardLink], "0") != ""
   180  	// TODO Handle error from determineFeature
   181  	feature, _ := determineFeature(fname, mh)
   182  	if feature == featureRegular {
   183  		if ext := path.Ext(fname); ext != "" {
   184  			if style, ok := lc.styleForExt[ext]; ok {
   185  				return style
   186  			}
   187  		}
   188  	}
   189  	return lc.styleForFeature[feature]
   190  }
   191  
   192  func init() {
   193  	defaultLsColor = parseLsColor(defaultLsColorString)
   194  }