github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/doltcore/doltdocs/docs.go (about)

     1  // Copyright 2020 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package doltdocs
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"path/filepath"
    22  
    23  	"github.com/dolthub/dolt/go/libraries/doltcore/dbfactory"
    24  	"github.com/dolthub/dolt/go/libraries/doltcore/doltdb"
    25  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    26  	"github.com/dolthub/dolt/go/libraries/utils/filesys"
    27  	"github.com/dolthub/dolt/go/store/types"
    28  )
    29  
    30  var ErrDocsUpdate = errors.New("error updating local docs")
    31  var ErrEmptyDocsTable = errors.New("error: All docs removed. Removing Docs Table")
    32  var ErrMarshallingSchema = errors.New("error marshalling schema")
    33  
    34  var doltDocsColumns = schema.NewColCollection(
    35  	schema.NewColumn(doltdb.DocPkColumnName, schema.DocNameTag, types.StringKind, true, schema.NotNullConstraint{}),
    36  	schema.NewColumn(doltdb.DocTextColumnName, schema.DocTextTag, types.StringKind, false),
    37  )
    38  var Schema = schema.MustSchemaFromCols(doltDocsColumns)
    39  
    40  type Doc struct {
    41  	Text  []byte
    42  	DocPk string
    43  	File  string
    44  }
    45  
    46  type Docs []Doc
    47  
    48  const (
    49  	ReadmeFile  = "../README.md"
    50  	LicenseFile = "../LICENSE.md"
    51  
    52  	// LicenseDoc is the key for accessing the license within the docs table
    53  	LicenseDoc = "LICENSE.md"
    54  	// ReadmeDoc is the key for accessing the readme within the docs table
    55  	ReadmeDoc = "README.md"
    56  )
    57  
    58  var SupportedDocs = Docs{
    59  	{DocPk: ReadmeDoc, File: ReadmeFile},
    60  	{DocPk: LicenseDoc, File: LicenseFile},
    61  }
    62  
    63  // GetLocalFileText returns a byte slice representing the contents of the provided file, if it exists
    64  func GetLocalFileText(fs filesys.Filesys, file string) ([]byte, error) {
    65  	path := ""
    66  	if DocFileExists(fs, file) {
    67  		path = GetDocFilePath(file)
    68  	}
    69  
    70  	if path != "" {
    71  		return fs.ReadFile(path)
    72  	}
    73  
    74  	return nil, nil
    75  }
    76  
    77  // GetSupportedDocs takes in a filesystem and returns the contents of all docs on disk.
    78  func GetSupportedDocs(fs filesys.Filesys) (docs Docs, err error) {
    79  	docs = Docs{}
    80  	for _, doc := range SupportedDocs {
    81  		newerText, err := GetLocalFileText(fs, doc.File)
    82  		if err != nil {
    83  			return nil, err
    84  		}
    85  		doc.Text = newerText
    86  		docs = append(docs, doc)
    87  	}
    88  	return docs, nil
    89  }
    90  
    91  // GetDoc takes in a filesystem and a docName and returns the doc's contents.
    92  func GetDoc(fs filesys.Filesys, docName string) (doc Doc, err error) {
    93  	for _, doc := range SupportedDocs {
    94  		if doc.DocPk == docName {
    95  			newerText, err := GetLocalFileText(fs, doc.File)
    96  			if err != nil {
    97  				return Doc{}, err
    98  			}
    99  			doc.Text = newerText
   100  			return doc, nil
   101  		}
   102  	}
   103  	return Doc{}, err
   104  }
   105  
   106  func GetDocNamesFromDocs(docs Docs) []string {
   107  	if docs == nil {
   108  		return nil
   109  	}
   110  
   111  	ret := make([]string, len(docs))
   112  
   113  	for i, doc := range docs {
   114  		ret[i] = doc.DocPk
   115  	}
   116  
   117  	return ret
   118  }
   119  
   120  // GetDocsFromRoot takes in a root value and returns the docs stored in its dolt_docs table.
   121  func GetDocsFromRoot(ctx context.Context, root *doltdb.RootValue, docNames ...string) (Docs, error) {
   122  	docTbl, docTblFound, err := root.GetTable(ctx, doltdb.DocTableName)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	var sch schema.Schema
   128  	if docTblFound {
   129  		docSch, err := docTbl.GetSchema(ctx)
   130  		if err != nil {
   131  			return nil, err
   132  		}
   133  		sch = docSch
   134  	}
   135  
   136  	if docNames == nil {
   137  		docNames = GetDocNamesFromDocs(SupportedDocs)
   138  	}
   139  
   140  	docs := make(Docs, len(docNames))
   141  	for i, name := range docNames {
   142  		doc, isSupported := IsSupportedDoc(name)
   143  		if !isSupported {
   144  			return nil, fmt.Errorf("%s is not a supported doc", name)
   145  		}
   146  
   147  		docText, err := getDocTextFromTbl(ctx, docTbl, &sch, name)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  
   152  		doc.Text = docText
   153  		docs[i] = doc
   154  	}
   155  
   156  	return docs, nil
   157  }
   158  
   159  // Save takes in a fs object and saves all the docs to the filesystem, overwriting any existing files.
   160  func (docs Docs) Save(fs filesys.ReadWriteFS) error {
   161  	for _, doc := range docs {
   162  		if _, ok := IsSupportedDoc(doc.DocPk); !ok {
   163  			continue
   164  		}
   165  		filePath := GetDocFilePath(doc.File)
   166  		if doc.Text != nil {
   167  			err := fs.WriteFile(filePath, doc.Text)
   168  			if err != nil {
   169  				return err
   170  			}
   171  		} else {
   172  			err := DeleteDoc(fs, doc.DocPk)
   173  			if err != nil {
   174  				return err
   175  			}
   176  		}
   177  	}
   178  	return nil
   179  }
   180  
   181  // GetDocFilePath takes in a filename and appends it to the DoltDir filepath.
   182  func GetDocFilePath(filename string) string {
   183  	return filepath.Join(dbfactory.DoltDir, filename)
   184  }
   185  
   186  // LoadDocs takes in a fs object and reads all the docs (ex. README.md) defined in SupportedDocs.
   187  func LoadDocs(fs filesys.ReadWriteFS) (Docs, error) {
   188  	docsWithCurrentText := SupportedDocs
   189  	for i, val := range docsWithCurrentText {
   190  		path := GetDocFilePath(val.File)
   191  		exists, isDir := fs.Exists(path)
   192  		if exists && !isDir {
   193  			data, err := fs.ReadFile(path)
   194  			if err != nil {
   195  				return nil, err
   196  			}
   197  			val.Text = data
   198  			docsWithCurrentText[i] = val
   199  		}
   200  	}
   201  	return docsWithCurrentText, nil
   202  }
   203  
   204  func IsSupportedDoc(docName string) (Doc, bool) {
   205  	for _, doc := range SupportedDocs {
   206  		if doc.DocPk == docName {
   207  			return doc, true
   208  		}
   209  	}
   210  	return Doc{}, false
   211  }
   212  
   213  func DocFileExists(fs filesys.ReadWriteFS, file string) bool {
   214  	exists, isDir := fs.Exists(GetDocFilePath(file))
   215  	return exists && !isDir
   216  }
   217  
   218  // DeleteDoc takes in a filesytem object and deletes the file with docName, if it's a SupportedDoc.
   219  func DeleteDoc(fs filesys.ReadWriteFS, docName string) error {
   220  	if doc, ok := IsSupportedDoc(docName); ok {
   221  		if doc.DocPk == docName {
   222  			path := GetDocFilePath(doc.File)
   223  			exists, isDir := fs.Exists(path)
   224  			if exists && !isDir {
   225  				return fs.DeleteFile(path)
   226  			}
   227  		}
   228  	}
   229  	return nil
   230  }
   231  
   232  // UpdateRootWithDocs takes in a root value, and some docs and writes those docs to the dolt_docs table
   233  // (perhaps creating it in the process). The table might not necessarily need to be created if there are no docs in the
   234  // repo yet.
   235  func UpdateRootWithDocs(ctx context.Context, root *doltdb.RootValue, docs Docs) (*doltdb.RootValue, error) {
   236  	docTbl, err := CreateOrUpdateDocsTable(ctx, root, docs)
   237  
   238  	if errors.Is(ErrEmptyDocsTable, err) {
   239  		root, err = root.RemoveTables(ctx, doltdb.DocTableName)
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  	} else if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	// There might not need be a need to create docs table if not docs have been created yet so check if docTbl != nil.
   248  	if docTbl != nil {
   249  		root, err = root.PutTable(ctx, doltdb.DocTableName, docTbl)
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  	}
   254  
   255  	return root, nil
   256  }