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 }