1// Copyright 2018 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// The txtar-c command archives a directory tree as a txtar archive printed to standard output.
6//
7// Usage:
8//
9//	txtar-c /path/to/dir >saved.txt
10//
11// See https://godoc.org/github.com/rogpeppe/go-internal/txtar for details of the format
12// and how to parse a txtar file.
13//
14package main
15
16import (
17	"bytes"
18	stdflag "flag"
19	"fmt"
20	"io/ioutil"
21	"log"
22	"os"
23	"path/filepath"
24	"strings"
25	"unicode/utf8"
26
27	"github.com/rogpeppe/go-internal/txtar"
28)
29
30var flag = stdflag.NewFlagSet(os.Args[0], stdflag.ContinueOnError)
31
32func usage() {
33	fmt.Fprintf(os.Stderr, "usage: txtar-c dir >saved.txt\n")
34	flag.PrintDefaults()
35}
36
37var (
38	quoteFlag = flag.Bool("quote", false, "quote files that contain txtar file markers instead of failing")
39	allFlag   = flag.Bool("a", false, "include dot files too")
40)
41
42func main() {
43	os.Exit(main1())
44}
45
46func main1() int {
47	flag.Usage = usage
48	if flag.Parse(os.Args[1:]) != nil {
49		return 2
50	}
51	if flag.NArg() != 1 {
52		usage()
53		return 2
54	}
55
56	log.SetPrefix("txtar-c: ")
57	log.SetFlags(0)
58
59	dir := flag.Arg(0)
60
61	a := new(txtar.Archive)
62	dir = filepath.Clean(dir)
63	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
64		if path == dir {
65			return nil
66		}
67		name := info.Name()
68		if strings.HasPrefix(name, ".") && !*allFlag {
69			if info.IsDir() {
70				return filepath.SkipDir
71			}
72			return nil
73		}
74		if !info.Mode().IsRegular() {
75			return nil
76		}
77		data, err := ioutil.ReadFile(path)
78		if err != nil {
79			log.Fatal(err)
80		}
81		if !utf8.Valid(data) {
82			log.Printf("%s: ignoring file with invalid UTF-8 data", path)
83			return nil
84		}
85		if len(data) > 0 && !bytes.HasSuffix(data, []byte("\n")) {
86			log.Printf("%s: adding final newline", path)
87			data = append(data, '\n')
88		}
89		filename := strings.TrimPrefix(path, dir+string(filepath.Separator))
90		if txtar.NeedsQuote(data) {
91			if !*quoteFlag {
92				log.Printf("%s: ignoring file with txtar marker in", path)
93				return nil
94			}
95			data, err = txtar.Quote(data)
96			if err != nil {
97				log.Printf("%s: ignoring unquotable file: %v", path, err)
98				return nil
99			}
100			a.Comment = append(a.Comment, []byte("unquote "+filename+"\n")...)
101		}
102		a.Files = append(a.Files, txtar.File{
103			Name: filepath.ToSlash(filename),
104			Data: data,
105		})
106		return nil
107	})
108
109	data := txtar.Format(a)
110	os.Stdout.Write(data)
111
112	return 0
113}
114