github.com/MontFerret/ferret@v0.18.0/pkg/stdlib/io/fs/write.go (about)

     1  package fs
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"sort"
     7  
     8  	"github.com/MontFerret/ferret/pkg/runtime/core"
     9  	"github.com/MontFerret/ferret/pkg/runtime/values"
    10  	"github.com/MontFerret/ferret/pkg/runtime/values/types"
    11  )
    12  
    13  // WRITE writes the given data into the file.
    14  // @param {String} path - File path to write into.
    15  // @param {Binary} data - Data to write.
    16  // @param {Object} [params] - additional parameters:
    17  // @param {String} [params.mode] - Write mode.
    18  // * x - Exclusive: returns an error if the file exist. It can be combined with other modes
    19  // * a - Append: will create a file if the specified file does not exist
    20  // * w - Write (Default): will create a file if the specified file does not exist
    21  func Write(_ context.Context, args ...core.Value) (core.Value, error) {
    22  	err := validateRequiredWriteArgs(args)
    23  
    24  	if err != nil {
    25  		return values.None, err
    26  	}
    27  
    28  	fpath := args[0].String()
    29  	arg1 := args[1]
    30  
    31  	var data []byte
    32  
    33  	if arg1.Type() == types.Binary {
    34  		data = arg1.(values.Binary)
    35  	} else {
    36  		data = []byte(arg1.String())
    37  	}
    38  
    39  	params := defaultParams
    40  
    41  	if len(args) == 3 {
    42  		params, err = parseParams(args[2])
    43  
    44  		if err != nil {
    45  			return values.None, core.Error(
    46  				err,
    47  				"parse `params` argument",
    48  			)
    49  		}
    50  	}
    51  
    52  	// 0666 - read & write
    53  	file, err := os.OpenFile(fpath, params.ModeFlag, 0666)
    54  
    55  	if err != nil {
    56  		return values.None, core.Error(err, "open file")
    57  	}
    58  
    59  	defer file.Close()
    60  
    61  	_, err = file.Write(data)
    62  
    63  	if err != nil {
    64  		return values.None, core.Error(err, "write file")
    65  	}
    66  
    67  	return values.None, nil
    68  }
    69  
    70  func validateRequiredWriteArgs(args []core.Value) error {
    71  	err := core.ValidateArgs(args, 2, 3)
    72  
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	err = core.ValidateType(args[0], types.String)
    78  
    79  	if err != nil {
    80  		return err
    81  	}
    82  
    83  	return nil
    84  }
    85  
    86  // parsedParams contains parsed additional parameters.
    87  type parsedParams struct {
    88  	ModeFlag int
    89  }
    90  
    91  var defaultParams = parsedParams{
    92  	// the same as `w`
    93  	ModeFlag: os.O_WRONLY | os.O_CREATE | os.O_TRUNC,
    94  }
    95  
    96  func parseParams(value core.Value) (parsedParams, error) {
    97  	err := core.ValidateType(value, types.Object)
    98  
    99  	if err != nil {
   100  		return parsedParams{}, err
   101  	}
   102  
   103  	obj := value.(*values.Object)
   104  
   105  	params := defaultParams
   106  
   107  	modestr, exists := obj.Get(values.NewString("mode"))
   108  
   109  	if exists {
   110  		flag, err := parseWriteMode(modestr.String())
   111  
   112  		if err != nil {
   113  			return parsedParams{}, core.Error(
   114  				core.ErrInvalidArgument,
   115  				"parse write mode",
   116  			)
   117  		}
   118  
   119  		params.ModeFlag = flag
   120  	}
   121  
   122  	return params, nil
   123  }
   124  
   125  func parseWriteMode(s string) (int, error) {
   126  	letters := []rune(s)
   127  	count := len(letters)
   128  
   129  	if count == 0 || count > 2 {
   130  		return -1, core.Errorf(
   131  			core.ErrInvalidArgument,
   132  			"must be from 1 to 2 mode letters, got `%d`", count,
   133  		)
   134  	}
   135  
   136  	// sort letters for more convenient work with it
   137  	sort.Slice(letters, func(i, j int) bool { return letters[i] < letters[j] })
   138  
   139  	// minimum flag for writing to file
   140  	flag := os.O_WRONLY | os.O_CREATE
   141  
   142  	if count == 2 {
   143  		// since letter is sorted, `x` will always be the letters[1]
   144  		if letters[1] != 'x' {
   145  			return -1, core.Errorf(
   146  				core.ErrInvalidArgument,
   147  				"invalid mode `%s`", s,
   148  			)
   149  		}
   150  
   151  		flag |= os.O_EXCL
   152  	}
   153  
   154  	switch letters[0] {
   155  	case 'a':
   156  		flag |= os.O_APPEND
   157  	case 'w':
   158  		flag |= os.O_TRUNC
   159  	default:
   160  		return -1, core.Errorf(
   161  			core.ErrInvalidArgument,
   162  			"invalid mode `%s`", s,
   163  		)
   164  	}
   165  
   166  	return flag, nil
   167  }