Module shell (1)
This commit is contained in:
		
							parent
							
								
									57f6071552
								
							
						
					
					
						commit
						898822d78f
					
				
					 3 changed files with 646 additions and 0 deletions
				
			
		
							
								
								
									
										333
									
								
								shell/file/file.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								shell/file/file.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,333 @@
 | 
			
		|||
package file
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/user"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	. "gitea.zaclys.com/bvaudour/gob/option"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	owners = make(map[string]string)
 | 
			
		||||
	groups = make(map[string]string)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// File représente la description d’un fichier sur un disque.
 | 
			
		||||
type File struct {
 | 
			
		||||
	info     os.FileInfo
 | 
			
		||||
	dirname  string
 | 
			
		||||
	name     string
 | 
			
		||||
	parent   Option[*File]
 | 
			
		||||
	children FileList
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New récupère les informations d’un fichier à partir de son nom.
 | 
			
		||||
// et de son répertoire d’appartenance.
 | 
			
		||||
func New(dirname, name string) (f Result[*File]) {
 | 
			
		||||
	info, err := os.Lstat(filepath.Join(dirname, name))
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return Ok(&File{
 | 
			
		||||
			info:    info,
 | 
			
		||||
			dirname: dirname,
 | 
			
		||||
			name:    name,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return Err[*File](err)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewFromInfo agit comme New mais en ayant déjà récupéré une
 | 
			
		||||
// partie de la description.
 | 
			
		||||
func NewFromInfo(dirname string, info os.FileInfo) *File {
 | 
			
		||||
	return &File{
 | 
			
		||||
		info:    info,
 | 
			
		||||
		dirname: dirname,
 | 
			
		||||
		name:    info.Name(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (f *File) stat() *syscall.Stat_t { return f.info.Sys().(*syscall.Stat_t) }
 | 
			
		||||
 | 
			
		||||
// Mod retourne les droits UNIX du fichier.
 | 
			
		||||
func (f *File) Mode() os.FileMode { return f.info.Mode() }
 | 
			
		||||
 | 
			
		||||
// HasMode vérifie que le fichier a un des droits spécifiés.
 | 
			
		||||
func (f *File) HasMode(mask os.FileMode) bool { return f.Mode()&mask != 0 }
 | 
			
		||||
 | 
			
		||||
// IsDir retourne vrai si le fichier est un répertoire.
 | 
			
		||||
func (f *File) IsDir() bool { return f.info.IsDir() }
 | 
			
		||||
 | 
			
		||||
// IsSymlink retourne vrai si le fichier est un lien symbolique.
 | 
			
		||||
func (f *File) IsSymlink() bool { return f.HasMode(os.ModeSymlink) }
 | 
			
		||||
 | 
			
		||||
// IsDevice retourne vrai si le fichier est un fichier de périphérique.
 | 
			
		||||
func (f *File) IsDevice() bool { return f.HasMode(os.ModeDevice) }
 | 
			
		||||
 | 
			
		||||
// IsCharDevice retourne vrai si le fichier est un fichier de périphérique de caractère.
 | 
			
		||||
func (f *File) IsCharDevice() bool { return f.HasMode(os.ModeCharDevice) }
 | 
			
		||||
 | 
			
		||||
// IsSocket retourne vrai si le fichier est un socket.
 | 
			
		||||
func (f *File) IsSocket() bool { return f.HasMode(os.ModeSocket) }
 | 
			
		||||
 | 
			
		||||
// IsPipe retourne vrai si le fichier est de type pipe.
 | 
			
		||||
func (f *File) IsPipe() bool { return f.HasMode(os.ModeNamedPipe) }
 | 
			
		||||
 | 
			
		||||
// IsIrregular retourne vrai si fichier est de type inconnu.
 | 
			
		||||
func (f *File) IsIrregular() bool { return f.HasMode(os.ModeIrregular) }
 | 
			
		||||
 | 
			
		||||
// IsRegular retourne vrai si le fichier est un fichier régulier.
 | 
			
		||||
func (f *File) IsRegular() bool { return f.Mode().IsRegular() }
 | 
			
		||||
 | 
			
		||||
// IsUid retourne vrai si le fichier a la permission Suid.
 | 
			
		||||
func (f *File) IsUid() bool { return f.HasMode(os.ModeSetuid) }
 | 
			
		||||
 | 
			
		||||
// IsGid retourne vrai si le fichier a la permission Sgid.
 | 
			
		||||
func (f *File) IsGid() bool { return f.HasMode(os.ModeSetgid) }
 | 
			
		||||
 | 
			
		||||
// IsSticky retourne vrai si le fichier a la permission Sticky Bit.
 | 
			
		||||
func (f *File) IsSticky() bool { return f.HasMode(os.ModeSticky) }
 | 
			
		||||
 | 
			
		||||
// Name retourne le nom du fichier.
 | 
			
		||||
func (f *File) Name() string { return f.name }
 | 
			
		||||
 | 
			
		||||
// DirName retourne le répertoire d’appartenance.
 | 
			
		||||
func (f *File) DirName() string { return f.dirname }
 | 
			
		||||
 | 
			
		||||
// Path retourne le chemin complet du fichier.
 | 
			
		||||
func (f *File) Path() string { return filepath.Join(f.dirname, f.name) }
 | 
			
		||||
 | 
			
		||||
// AbsolutePath agit comme Path mais s’assure que le chemin soit absolu.
 | 
			
		||||
func (f *File) AbsolutePath() string {
 | 
			
		||||
	path := f.Path()
 | 
			
		||||
	if filepath.IsAbs(path) {
 | 
			
		||||
		return path
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	abs, _ := filepath.Abs(path)
 | 
			
		||||
	return abs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Split retourne le dossier et le nom du fichier.
 | 
			
		||||
func (f *File) Split() (dirname, name string) {
 | 
			
		||||
	path := filepath.Clean(f.Path())
 | 
			
		||||
 | 
			
		||||
	return filepath.Split(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Split retourne le dossier absolu et le nom du fichier.
 | 
			
		||||
func (f *File) AbsoluteSplit() (dirname, name string) {
 | 
			
		||||
	path := f.AbsolutePath()
 | 
			
		||||
 | 
			
		||||
	return filepath.Split(path)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Extension retourne l’extension du fichier.
 | 
			
		||||
func (f *File) Extension() string {
 | 
			
		||||
	if f.IsDir() {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return filepath.Ext(f.info.Name())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// LinkPath retourne le chemin du fichier pointé
 | 
			
		||||
// par le lien symbolique, ou une chaîne vide si le lien est cassé
 | 
			
		||||
// ou que le fichier n’est pas un lien symbolique.
 | 
			
		||||
func (f *File) LinkPath() string {
 | 
			
		||||
	if !f.IsSymlink() {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lpath, _ := os.Readlink(f.Path())
 | 
			
		||||
	return lpath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ts2Date(ts syscall.Timespec) time.Time { return time.Unix(ts.Sec, ts.Nsec) }
 | 
			
		||||
 | 
			
		||||
// CreationTime retourne la date de création du fichier.
 | 
			
		||||
func (f *File) CreationTime() time.Time { return ts2Date(f.stat().Ctim) }
 | 
			
		||||
 | 
			
		||||
// AccessTime retourne la date d’accès du fichier.
 | 
			
		||||
func (f *File) AccessTime() time.Time { return ts2Date(f.stat().Atim) }
 | 
			
		||||
 | 
			
		||||
// ModificationTime retourne la date de modification du fichier.
 | 
			
		||||
func (f *File) ModificationTime() time.Time { return f.info.ModTime() }
 | 
			
		||||
 | 
			
		||||
// Size retourne la taille (en octets) du fichier.
 | 
			
		||||
func (f *File) Size() int64 { return f.info.Size() }
 | 
			
		||||
 | 
			
		||||
// BlockSize retourne la taille de blocs (en octets) du fichier.
 | 
			
		||||
func (f *File) BlockSize() int64 { return f.stat().Blksize }
 | 
			
		||||
 | 
			
		||||
// DirSize retourne la taille totale des fichiers (en octets) contenus dans un répertoire,
 | 
			
		||||
// ou bien la taille du fichier s’il s’agit d’un simple fichier.
 | 
			
		||||
// La taille inclut de façon récursive celle des sous-répertoires.
 | 
			
		||||
func (f *File) DirSize() (size int64) {
 | 
			
		||||
	if !f.IsDir() {
 | 
			
		||||
		return f.Size()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, c := range f.children {
 | 
			
		||||
		size += c.DirSize()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// OwnerID retourne l’ID utilisateur du fichier.
 | 
			
		||||
func (f *File) OwnerID() string { return strconv.Itoa(int(f.stat().Uid)) }
 | 
			
		||||
 | 
			
		||||
// GroupID retourne l’ID du groupe du fichier.
 | 
			
		||||
func (f *File) GroupID() string { return strconv.Itoa(int(f.stat().Gid)) }
 | 
			
		||||
 | 
			
		||||
// Owner retourne le nom du propriétaire du fichier.
 | 
			
		||||
func (f *File) Owner() string {
 | 
			
		||||
	id := f.OwnerID()
 | 
			
		||||
	if owner, ok := owners[id]; ok {
 | 
			
		||||
		return owner
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var owner string
 | 
			
		||||
	if e, err := user.LookupId(id); err == nil {
 | 
			
		||||
		owner = e.Name
 | 
			
		||||
	}
 | 
			
		||||
	owners[id] = owner
 | 
			
		||||
 | 
			
		||||
	return owner
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Group retourne le nom du groupe du fichier.
 | 
			
		||||
func (f *File) Group() string {
 | 
			
		||||
	id := f.GroupID()
 | 
			
		||||
	if group, ok := groups[id]; ok {
 | 
			
		||||
		return group
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var group string
 | 
			
		||||
	if e, err := user.LookupGroupId(id); err == nil {
 | 
			
		||||
		group = e.Name
 | 
			
		||||
	}
 | 
			
		||||
	groups[id] = group
 | 
			
		||||
 | 
			
		||||
	return group
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Children retourne la liste des fichiers de premier niveau du répertoire
 | 
			
		||||
// ou rien si le fichier n’est pas un répertoire.
 | 
			
		||||
func (f *File) Children() FileList { return f.children }
 | 
			
		||||
 | 
			
		||||
// SearchChildren recherche les fichiers d’un répertoire.
 | 
			
		||||
// deepness indique la profondeur de recherche et doit valoir au moins 1.
 | 
			
		||||
// searchoptions peut contenir deux options de recherche optionnelles :
 | 
			
		||||
// 1. Fichiers masqués (par défaut non retournés),
 | 
			
		||||
// 2. Fichiers de backup (ie. ceux avec le suffixe ~) (par défaut non retournés).
 | 
			
		||||
func (f *File) SearchChildren(deepness int, searchOptions ...bool) (children FileList) {
 | 
			
		||||
	if !f.IsDir() || deepness == 0 {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var hidden, backup bool
 | 
			
		||||
	if len(searchOptions) > 0 {
 | 
			
		||||
		hidden = searchOptions[0]
 | 
			
		||||
		if len(searchOptions) > 1 {
 | 
			
		||||
			backup = searchOptions[1]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir, err := os.Open(f.Path())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer dir.Close()
 | 
			
		||||
 | 
			
		||||
	infos, _ := dir.Readdir(0)
 | 
			
		||||
	dirname := f.Path()
 | 
			
		||||
	if deepness > 0 {
 | 
			
		||||
		deepness--
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	for _, i := range infos {
 | 
			
		||||
		name := i.Name()
 | 
			
		||||
		if (!hidden && strings.HasPrefix(name, ".")) || (!backup && strings.HasSuffix(name, "~")) {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		child := NewFromInfo(dirname, i)
 | 
			
		||||
		children.Add(child)
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go (func(c *File) {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			c.SearchChildren(deepness, searchOptions...)
 | 
			
		||||
		})(child)
 | 
			
		||||
	}
 | 
			
		||||
	wg.Wait()
 | 
			
		||||
 | 
			
		||||
	f.children = children
 | 
			
		||||
	for _, c := range children {
 | 
			
		||||
		c.parent = Some(f)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Flatten retourne la liste de tous les fichiers et répertoires de façon récursive.
 | 
			
		||||
// Si only_dir est présent et vaut true, seuls les répertoires sont retournés.
 | 
			
		||||
func (f *File) Flatten(only_dirs ...bool) (fl FileList) {
 | 
			
		||||
	d := len(only_dirs) > 0 && only_dirs[0]
 | 
			
		||||
	if d && !f.IsDir() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fl.Add(f)
 | 
			
		||||
	for _, c := range f.children {
 | 
			
		||||
		fl.Add(c.Flatten(only_dirs...)...)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Parent retourne le répertoire d’appartenance.
 | 
			
		||||
func (f *File) Parent() Option[*File] {
 | 
			
		||||
	return f.parent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchParent agit comme Parent mais force la recherche
 | 
			
		||||
// si celui-ci n’est pas encore défini.
 | 
			
		||||
func (f *File) SearchParent() Option[*File] {
 | 
			
		||||
	if f.parent.IsDefined() {
 | 
			
		||||
		return f.parent
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	d, _ := f.AbsoluteSplit()
 | 
			
		||||
	pdirname, pname := filepath.Split(d)
 | 
			
		||||
	p := New(pdirname, pname)
 | 
			
		||||
	if parent, ok := p.Ok(); ok {
 | 
			
		||||
		f.parent = Some(parent)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return f.parent
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddChildren ajoute des fichiers en tant qu’appartenance à un répertoire.
 | 
			
		||||
func (f *File) AddChildren(children ...*File) {
 | 
			
		||||
	for _, c := range children {
 | 
			
		||||
		c.parent = Some(f)
 | 
			
		||||
	}
 | 
			
		||||
	f.children.Add(children...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SetParent définit le répertoire parent du fichier.
 | 
			
		||||
// Si both est donné et est vrai, le fichier est ajouté au répertoire
 | 
			
		||||
// parent en tant qu’enfant.
 | 
			
		||||
func (f *File) SetParent(parent Option[*File], both ...bool) {
 | 
			
		||||
	f.parent = parent
 | 
			
		||||
	if p, ok := parent.Get(); ok && len(both) > 0 && both[0] {
 | 
			
		||||
		p.children.Add(f)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										188
									
								
								shell/file/filelist.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								shell/file/filelist.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,188 @@
 | 
			
		|||
package file
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"cmp"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"gitea.zaclys.com/bvaudour/gob/compare"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// FileList représente une liste de fichiers.
 | 
			
		||||
type FileList []*File
 | 
			
		||||
 | 
			
		||||
func (fl *FileList) Add(files ...*File) { *fl = append(*fl, files...) }
 | 
			
		||||
 | 
			
		||||
// CmpFunc est une fonction comparant deux répertoires.
 | 
			
		||||
// Elle peut être utilisée pour trier des répertoires.
 | 
			
		||||
type CmpFunc func(*File, *File) int
 | 
			
		||||
 | 
			
		||||
// CmpAll agrège plusieurs fonctions de comparaison
 | 
			
		||||
// en une seule.
 | 
			
		||||
func CmpAll(args ...CmpFunc) CmpFunc {
 | 
			
		||||
	return func(f1, f2 *File) int {
 | 
			
		||||
		for _, cb := range args {
 | 
			
		||||
			if c := cb(f1, f2); c != 0 {
 | 
			
		||||
				return c
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReverseCmp inverse la fonction de comparaison.
 | 
			
		||||
func ReverseCmp(cb CmpFunc) CmpFunc {
 | 
			
		||||
	return func(f1, f2 *File) int {
 | 
			
		||||
		return -cb(f1, f2)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Sort trie la liste de fichier selon la fonction de comparaison.
 | 
			
		||||
func (fl FileList) Sort(cb CmpFunc) FileList {
 | 
			
		||||
	if cb == nil {
 | 
			
		||||
		return fl
 | 
			
		||||
	}
 | 
			
		||||
	less := func(i, j int) bool { return cb(fl[i], fl[j]) < 0 }
 | 
			
		||||
	sort.Slice(fl, less)
 | 
			
		||||
	return fl
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func tr(e1, e2 string, t compare.TransformFunc) []string { return []string{t(e1), t(e2)} }
 | 
			
		||||
 | 
			
		||||
func trAll(e1, e2 string) [][]string {
 | 
			
		||||
	a := tr(e1, e2, compare.ToAscii)
 | 
			
		||||
	ai := tr(a[0], a[1], compare.ToLower)
 | 
			
		||||
	i := tr(e1, e2, compare.ToLower)
 | 
			
		||||
	return [][]string{ai, a, i, []string{e1, e2}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func trf(f1, f2 *File) (h1, h2 bool, data [][]string) {
 | 
			
		||||
	n1, n2 := f1.name, f2.name
 | 
			
		||||
	h1, h2 = len(n1) > 0 && n1[0] == '.', len(n2) > 0 && n2[0] == '.'
 | 
			
		||||
	if h1 {
 | 
			
		||||
		n1 = n1[1:]
 | 
			
		||||
	}
 | 
			
		||||
	if h2 {
 | 
			
		||||
		n2 = n2[1:]
 | 
			
		||||
	}
 | 
			
		||||
	data = trAll(n1, n2)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpStrings(data [][]string, cb compare.CompareFunc) int {
 | 
			
		||||
	for _, d := range data {
 | 
			
		||||
		if c := cb(d[0], d[1]); c != 0 {
 | 
			
		||||
			return c
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpRoot(f1, f2 *File) (int, bool) {
 | 
			
		||||
	switch {
 | 
			
		||||
	case f1.name == f2.name:
 | 
			
		||||
		return 0, true
 | 
			
		||||
	case f1.name == ".":
 | 
			
		||||
		return -1, true
 | 
			
		||||
	case f2.name == ".":
 | 
			
		||||
		return 1, true
 | 
			
		||||
	case f1.name == "..":
 | 
			
		||||
		return -1, true
 | 
			
		||||
	case f2.name == "..":
 | 
			
		||||
		return 1, true
 | 
			
		||||
	}
 | 
			
		||||
	return 0, false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpHidden(h1, h2 bool) int {
 | 
			
		||||
	if h1 != h2 {
 | 
			
		||||
		if h1 {
 | 
			
		||||
			return -1
 | 
			
		||||
		}
 | 
			
		||||
		return 1
 | 
			
		||||
	}
 | 
			
		||||
	return 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpInt(cb func(*File) int64) CmpFunc {
 | 
			
		||||
	return func(f1, f2 *File) int {
 | 
			
		||||
		e1, e2 := cb(f1), cb(f2)
 | 
			
		||||
 | 
			
		||||
		return cmp.Compare(e1, e2)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpDate(cb func(*File) time.Time) CmpFunc {
 | 
			
		||||
	return func(f1, f2 *File) int {
 | 
			
		||||
		e1, e2 := cb(f1), cb(f2)
 | 
			
		||||
 | 
			
		||||
		return cmp.Compare(e1.Unix(), e2.Unix())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func cmpName(cb compare.CompareFunc) CmpFunc {
 | 
			
		||||
	return func(f1, f2 *File) int {
 | 
			
		||||
		if c, ok := cmpRoot(f1, f2); ok {
 | 
			
		||||
			return c
 | 
			
		||||
		}
 | 
			
		||||
		h1, h2, data := trf(f1, f2)
 | 
			
		||||
		if c := cmpStrings(data, cb); c != 0 {
 | 
			
		||||
			return c
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return cmpHidden(h1, h2)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpDir place les répertoires en premier.
 | 
			
		||||
func CmpDir(f1, f2 *File) int {
 | 
			
		||||
	d1, d2 := f1.IsDir(), f2.IsDir()
 | 
			
		||||
 | 
			
		||||
	if d1 == d2 {
 | 
			
		||||
		return 0
 | 
			
		||||
	} else if d1 {
 | 
			
		||||
		return -1
 | 
			
		||||
	}
 | 
			
		||||
	return 1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpExt compare deux fichiers de même nom par leur extension.
 | 
			
		||||
func CmpExt(f1, f2 *File) int {
 | 
			
		||||
	return compare.CompareInsensitive(f1.Extension(), f2.Extension())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpSize compare deux fichiers par leur taille.
 | 
			
		||||
func CmpSize(f1, f2 *File) int {
 | 
			
		||||
	return cmpInt(func(f *File) int64 { return f.Size() })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpBlockSize compare deux fichiers par leur taille de bloc.
 | 
			
		||||
func CmpBlockSize(f1, f2 *File) int {
 | 
			
		||||
	return cmpInt(func(f *File) int64 { return f.BlockSize() })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpCreationTime compare deux fichiers par leur date de création.
 | 
			
		||||
func CmpCreationTime(f1, f2 *File) int {
 | 
			
		||||
	return cmpDate(func(f *File) time.Time { return f.CreationTime() })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpAccessTime compare deux fichiers par leur date d’accès.
 | 
			
		||||
func CmpAccessTime(f1, f2 *File) int {
 | 
			
		||||
	return cmpDate(func(f *File) time.Time { return f.AccessTime() })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpModificationTime compare deux fichiers par leur date de modification.
 | 
			
		||||
func CmpModificationTime(f1, f2 *File) int {
 | 
			
		||||
	return cmpDate(func(f *File) time.Time { return f.ModificationTime() })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpName compare deux fichiers par leur nom.
 | 
			
		||||
func CmpName(f1, f2 *File) int {
 | 
			
		||||
	return cmpName(strings.Compare)(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CmpNatural compare deux fichier par leur nom de manière naturelle.
 | 
			
		||||
func CmpNatural(f1, f2 *File) int {
 | 
			
		||||
	return cmpName(func(e1, e2 string) int { return compare.Natural(e1, e2) })(f1, f2)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										125
									
								
								shell/scanner/scanner.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								shell/scanner/scanner.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,125 @@
 | 
			
		|||
package scanner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"unicode/utf8"
 | 
			
		||||
 | 
			
		||||
	"gitea.zaclys.com/bvaudour/gob/collection"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Tokenizer est une interface implémentant une fonction de splittage.
 | 
			
		||||
type Tokenizer interface {
 | 
			
		||||
	Split(data []byte, atEOF bool) (advance int, token []byte, err error)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type tk struct {
 | 
			
		||||
	quote  bool
 | 
			
		||||
	escape bool
 | 
			
		||||
	spaces []rune
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runeToBytes(r rune) []byte {
 | 
			
		||||
	l := utf8.RuneLen(r)
 | 
			
		||||
	b := make([]byte, l)
 | 
			
		||||
	utf8.EncodeRune(b, r)
 | 
			
		||||
	return b
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (t tk) Split(data []byte, atEOF bool) (advance int, token []byte, err error) {
 | 
			
		||||
	q, e := collection.NewSet[rune](), collection.NewSet[rune]()
 | 
			
		||||
	s := collection.NewSet(t.spaces...)
 | 
			
		||||
	if t.quote {
 | 
			
		||||
		q.Add('\'', '"')
 | 
			
		||||
	}
 | 
			
		||||
	if t.escape {
 | 
			
		||||
		e.Add('\\')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Skip leading spaces.
 | 
			
		||||
	start := 0
 | 
			
		||||
	for width := 0; start < len(data); start += width {
 | 
			
		||||
		var r rune
 | 
			
		||||
		r, width = utf8.DecodeRune(data[start:])
 | 
			
		||||
		if !s.Contains(r) {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var quote, esc rune
 | 
			
		||||
 | 
			
		||||
	// Scan until space, marking end of word.
 | 
			
		||||
	for width, i := 0, start; i < len(data); i += width {
 | 
			
		||||
		var r rune
 | 
			
		||||
		r, width = utf8.DecodeRune(data[i:])
 | 
			
		||||
		if s.Contains(r) && !q.Contains(quote) && !e.Contains(esc) {
 | 
			
		||||
			return i + width, token, nil
 | 
			
		||||
		}
 | 
			
		||||
		if e.Contains(esc) {
 | 
			
		||||
			if q.Contains(quote) && !e.Contains(r) && r != quote {
 | 
			
		||||
				token = append(token, runeToBytes(esc)...)
 | 
			
		||||
			}
 | 
			
		||||
			token = append(token, runeToBytes(r)...)
 | 
			
		||||
			esc = 0
 | 
			
		||||
		} else if e.Contains(r) {
 | 
			
		||||
			esc = r
 | 
			
		||||
		} else if q.Contains(r) {
 | 
			
		||||
			if !q.Contains(quote) {
 | 
			
		||||
				quote = r
 | 
			
		||||
			} else if quote == r {
 | 
			
		||||
				quote = 0
 | 
			
		||||
			} else {
 | 
			
		||||
				token = append(token, runeToBytes(r)...)
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			token = append(token, runeToBytes(r)...)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// If we're at EOF, we have a final, non-empty, non-terminated word. Return it.
 | 
			
		||||
	if atEOF && len(data) > start {
 | 
			
		||||
		if e.Contains(esc) || q.Contains(quote) {
 | 
			
		||||
			return start, nil, errors.New("Incomplete token")
 | 
			
		||||
		}
 | 
			
		||||
		return len(data), token, nil
 | 
			
		||||
	}
 | 
			
		||||
	// Request more data.
 | 
			
		||||
	return start, nil, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewTokenizer retourne un tokenizer d’arguments de ligne de commande.
 | 
			
		||||
//
 | 
			
		||||
// Le split s’effectue au niveau des caractères spaces fourni (tous les espaces
 | 
			
		||||
// si aucun de fourni) sauf si ces caractères sont échappés
 | 
			
		||||
// (si escape) ou entre guillemets (si quote)
 | 
			
		||||
// Par exemple, prenons la chaîne suivante :
 | 
			
		||||
// unmot une\ phrase "une deuxième\" phrase"
 | 
			
		||||
//
 | 
			
		||||
// Le résultat va être décomposé en 3 éléments :
 | 
			
		||||
// - unmot
 | 
			
		||||
// - une\ phrase
 | 
			
		||||
// - "une deuxième\" phrase"
 | 
			
		||||
func NewTokenizer(quote, escape bool, spaces ...rune) Tokenizer {
 | 
			
		||||
	if len(spaces) == 0 {
 | 
			
		||||
		spaces = []rune(" \t\n\v\f\r")
 | 
			
		||||
	}
 | 
			
		||||
	return tk{
 | 
			
		||||
		quote:  quote,
 | 
			
		||||
		escape: escape,
 | 
			
		||||
		spaces: spaces,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewScanner retourne un nouveau scanner utilisant le tokenizer spécifié
 | 
			
		||||
// pour la fonction de splitage (NewTokenizer(true, true) si aucun tokenizer fourni).
 | 
			
		||||
func NewScanner(r io.Reader, t ...Tokenizer) *bufio.Scanner {
 | 
			
		||||
	var s bufio.SplitFunc
 | 
			
		||||
	if len(t) > 0 {
 | 
			
		||||
		s = t[0].Split
 | 
			
		||||
	} else {
 | 
			
		||||
		s = (NewTokenizer(true, true)).Split
 | 
			
		||||
	}
 | 
			
		||||
	sc := bufio.NewScanner(r)
 | 
			
		||||
	sc.Split(s)
 | 
			
		||||
	return sc
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue