github.com/vertgenlab/gonomics@v1.0.0/fileio/easyio.go (about)

     1  package fileio
     2  
     3  import (
     4  	"bufio"
     5  	"compress/gzip"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"strings"
    11  
    12  	"github.com/vertgenlab/gonomics/exception"
    13  )
    14  
    15  // EasyReader provides a simplified wrapper of the builtin golang io functions.
    16  // Will silently handle reading gziped files. EasyReader is a valid io.Reader.
    17  type EasyReader struct {
    18  	File         *os.File
    19  	internalGzip *gzip.Reader
    20  	BuffReader   *bufio.Reader
    21  }
    22  
    23  // EasyWriter provides a simplified wrapper of the builtin golang io functions.
    24  // Will silently gzip output files when the input filename ends with '.gz'.
    25  // EasyWriter is a valid io.Writer.
    26  type EasyWriter struct {
    27  	file         *os.File
    28  	internalBuff *bufio.Writer
    29  	internalGzip *gzip.Writer
    30  }
    31  
    32  // EasyOpen opens the input file. Panics if errors are encountered.
    33  func EasyOpen(filename string) *EasyReader {
    34  	if strings.Contains(filename, "http") {
    35  		return EasyHttp(filename)
    36  	}
    37  
    38  	answer := EasyReader{}
    39  	var hasMagicGzip bool
    40  	var readerInput io.Reader
    41  
    42  	if strings.HasPrefix(filename, "stdin") {
    43  		// when reading stdin we will assume the input is gzipped
    44  		// if the file begins with the two magic gzip bytes 1f8d.
    45  		// If it does, append .gz to the filename so it is parsed
    46  		// as gzip in the following switch case.
    47  		answer.File = os.Stdin
    48  		readerInput, hasMagicGzip = newStdinMagicReader(magicGzip)
    49  		if hasMagicGzip {
    50  			filename += ".gz"
    51  		}
    52  	} else {
    53  		answer.File = MustOpen(filename)
    54  		hasMagicGzip = IsGzip(answer.File)
    55  		readerInput = answer.File
    56  	}
    57  
    58  	var err error
    59  	switch {
    60  	case strings.HasSuffix(filename, ".gz") && hasMagicGzip:
    61  		answer.internalGzip, err = gzip.NewReader(readerInput)
    62  		exception.PanicOnErr(err)
    63  		answer.BuffReader = bufio.NewReader(answer.internalGzip)
    64  
    65  	case strings.HasSuffix(filename, ".gz"):
    66  		log.Fatalf("ERROR: input file '%s' has the .gz suffix, but is not a gzip file", filename)
    67  
    68  	case hasMagicGzip:
    69  		log.Printf("WARNING: The input file '%s' looks like it may be gzipped, "+
    70  			"but does not have the .gz suffix. Processing as a non-gzip file. Add the .gz "+
    71  			"suffix to process as a gzip file.", filename)
    72  		fallthrough
    73  
    74  	default:
    75  		answer.BuffReader = bufio.NewReader(readerInput)
    76  	}
    77  
    78  	return &answer
    79  }
    80  
    81  // EasyCreate creates a file with the input name. Panics if errors are encountered.
    82  func EasyCreate(filename string) *EasyWriter {
    83  	answer := EasyWriter{}
    84  
    85  	switch {
    86  	case strings.HasPrefix(filename, "stdout"):
    87  		answer.file = os.Stdout
    88  	case strings.HasPrefix(filename, "stderr"):
    89  		answer.file = os.Stderr
    90  	default:
    91  		answer.file = MustCreate(filename)
    92  	}
    93  
    94  	answer.internalBuff = bufio.NewWriter(answer.file)
    95  
    96  	if strings.HasSuffix(filename, ".gz") {
    97  		answer.internalGzip = gzip.NewWriter(answer.internalBuff)
    98  	} else {
    99  		answer.internalGzip = nil
   100  	}
   101  	return &answer
   102  }
   103  
   104  // EasyNextLine returns the next line of the input EasyReader.
   105  // Returns true at EOF.
   106  func EasyNextLine(file *EasyReader) (string, bool) {
   107  	return NextLine(file.BuffReader)
   108  }
   109  
   110  // EasyNextRealLine returns the next line of the input EasyReader that does not begin with '#'.
   111  // Returns true at EOF.
   112  func EasyNextRealLine(file *EasyReader) (string, bool) {
   113  	return NextRealLine(file.BuffReader)
   114  }
   115  
   116  // EasyPeekReal will advance a reader past any lines beginning with '#' and read the first n bytes without advancing the reader.
   117  func EasyPeekReal(file *EasyReader, n int) ([]byte, error) {
   118  	return PeekReal(file.BuffReader, n)
   119  }
   120  
   121  // EasyReadHeader will read any leading comments lines from the file and return them as a slice of strings
   122  // with each element in the slice being a comment line.
   123  func EasyReadHeader(file *EasyReader) ([]string, error) {
   124  	return ReadHeader(file.BuffReader)
   125  }
   126  
   127  // EasyRemove deletes the input file.
   128  func EasyRemove(filename string) {
   129  	MustRemove(filename)
   130  }
   131  
   132  // Read inputs a file and returns each line in the file as a string.
   133  func Read(filename string) []string {
   134  	var answer []string
   135  	var err error
   136  	file := EasyOpen(filename)
   137  	reader := bufio.NewReader(file)
   138  	for line, doneReading := NextRealLine(reader); !doneReading; line, doneReading = NextRealLine(reader) {
   139  		answer = append(answer, line)
   140  	}
   141  	err = file.Close()
   142  	exception.PanicOnErr(err)
   143  	return answer
   144  }
   145  
   146  // WriteToFileHandle will write a string to an io.Writer and panic on
   147  // any error.
   148  func WriteToFileHandle(file io.Writer, rec string) {
   149  	var err error
   150  	_, err = fmt.Fprintf(file, "%s\n", rec)
   151  	exception.PanicOnErr(err)
   152  }
   153  
   154  // Write writes a slice of strings to a file with a newline
   155  // placed after each element of the slice.
   156  func Write(filename string, records []string) {
   157  	var err error
   158  	file := EasyCreate(filename)
   159  
   160  	for i := range records {
   161  		WriteToFileHandle(file, records[i])
   162  	}
   163  	err = file.Close()
   164  	exception.PanicOnErr(err)
   165  }