github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/builtins/core/open/open.go (about)

     1  package open
     2  
     3  import (
     4  	"compress/gzip"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"regexp"
     9  	"strings"
    10  
    11  	"github.com/lmorg/murex/lang"
    12  	"github.com/lmorg/murex/lang/stdio"
    13  	"github.com/lmorg/murex/lang/types"
    14  	"github.com/lmorg/murex/utils"
    15  )
    16  
    17  var rxExt = regexp.MustCompile(`(?i)\.([a-z0-9]+)(\.gz)?$`)
    18  
    19  func init() {
    20  	lang.DefineFunction("open", open, types.Any)
    21  }
    22  
    23  func open(p *lang.Process) (err error) {
    24  	var dataType string
    25  
    26  	if p.IsMethod {
    27  		return OpenPipe(p, p.Stdin)
    28  	}
    29  
    30  	path, err := p.Parameters.String(0)
    31  	if err != nil {
    32  		return err
    33  	}
    34  
    35  	closers, err := OpenFile(p, &path, &dataType)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	err = preview(p, path, dataType)
    41  	if err != nil {
    42  		return err
    43  	}
    44  
    45  	return CloseFiles(closers)
    46  }
    47  
    48  func OpenPipe(p *lang.Process, pipe stdio.Io) error {
    49  	dataType := pipe.GetDataType()
    50  	p.Stdout.SetDataType(dataType)
    51  
    52  	ext := GetExt("", dataType)
    53  	tmp, err := utils.NewTempFile(p.Stdin, ext)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	defer tmp.Close()
    58  
    59  	return preview(p, tmp.FileName, dataType)
    60  }
    61  
    62  func OpenFile(p *lang.Process, path *string, dataType *string) ([]io.Closer, error) {
    63  	var (
    64  		closers []io.Closer
    65  		err     error
    66  		ext     string
    67  	)
    68  
    69  	switch {
    70  	case utils.IsURL(*path):
    71  		var body io.ReadCloser
    72  		body, *dataType, err = http(p, *path)
    73  		if err != nil {
    74  			return closers, err
    75  		}
    76  
    77  		ext = GetExt("", *dataType)
    78  		tmp, err := utils.NewTempFile(body, ext)
    79  		if err != nil {
    80  			return closers, err
    81  		}
    82  
    83  		*path = tmp.FileName
    84  
    85  	default:
    86  		ext = GetExt(*path, "")
    87  		*dataType = lang.GetExtType(ext)
    88  	}
    89  
    90  	if *dataType == "gz" || (len(*path) > 3 && strings.ToLower((*path)[len(*path)-3:]) == ".gz") {
    91  		file, err := os.Open(*path)
    92  		if err != nil {
    93  			return closers, err
    94  		}
    95  		//defer file.Close()
    96  		closers = append(closers, file)
    97  
    98  		gz, err := gzip.NewReader(file)
    99  		if err != nil {
   100  			return closers, err
   101  		}
   102  		//defer gz.Close()
   103  		closers = append(closers, gz)
   104  
   105  		ext = GetExt(*path, "")
   106  		*dataType = lang.GetExtType(ext)
   107  		tmp, err := utils.NewTempFile(gz, ext)
   108  		//defer tmp.Close()
   109  		closers = append(closers, tmp)
   110  
   111  		if err != nil {
   112  			return closers, err
   113  		}
   114  
   115  		*path = tmp.FileName
   116  	}
   117  
   118  	return closers, err
   119  }
   120  
   121  func CloseFiles(closers []io.Closer) error {
   122  	var s string
   123  
   124  	for i := len(closers) - 1; i > -1; i-- {
   125  		err := closers[i].Close()
   126  		if err != nil {
   127  			s = fmt.Sprintf("%s: %s", err.Error(), s)
   128  		}
   129  	}
   130  
   131  	if len(s) > 0 {
   132  		return fmt.Errorf("unable to close files: %s", s[:len(s)-2])
   133  	}
   134  
   135  	return nil
   136  }
   137  
   138  func GetExt(path, dataType string) string {
   139  	if path != "" {
   140  		match := rxExt.FindAllStringSubmatch(path, -1)
   141  		if len(match) > 0 && len(match[0]) > 1 {
   142  			return strings.ToLower(match[0][1])
   143  		}
   144  	}
   145  
   146  	m := lang.GetFileExts()
   147  	for ext := range m {
   148  		if m[ext] == dataType {
   149  			return ext
   150  		}
   151  	}
   152  
   153  	return ""
   154  }
   155  
   156  func preview(p *lang.Process, path, dataType string) error {
   157  	if dataType == "" {
   158  		dataType = types.Generic
   159  	}
   160  
   161  	p.Stdout.SetDataType(dataType)
   162  	agent, err := OpenAgents.Get(dataType)
   163  
   164  	// we check if std(in|err) is a TTY because stdout might be a fake TTY in preview pane
   165  	if (p.Stdout.IsTTY() || (p.Stdin.IsTTY() && p.Stderr.IsTTY())) && dataType == types.Generic {
   166  		return openSystemCommand(p, path)
   167  	}
   168  
   169  	if !p.Stdout.IsTTY() || err != nil {
   170  		// Not a TTY or no open agent exists so fallback to passing []bytes along
   171  		file, err := os.Open(path)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		defer file.Close()
   177  
   178  		_, err = io.Copy(p.Stdout, file)
   179  		return err
   180  	}
   181  
   182  	fork := p.Fork(lang.F_FUNCTION | lang.F_NEW_MODULE | lang.F_NO_STDIN)
   183  	fork.Name.Set("open")
   184  	fork.Parameters.DefineParsed([]string{path})
   185  	fork.FileRef = agent.FileRef
   186  	_, err = fork.Execute(agent.Block)
   187  
   188  	if err != nil {
   189  		p.Stderr.Writeln([]byte("`open` code could not compile: " + err.Error()))
   190  	}
   191  
   192  	return err
   193  }