package document import ( "encoding/json" "fmt" "os" "path" "strings" ) func getDocumentFilepath(owner string) string { return fmt.Sprintf("./docs/%s.json", owner) } type fileDocument struct { owner string root map[string]interface{} dirty bool } // LoadFileDocument : 파일로 도큐먼트 로딩 func LoadFileDocument(owner string) (Document, error) { bt, err := os.ReadFile(getDocumentFilepath(owner)) if err != nil && !os.IsNotExist(err) { return nil, err } if len(bt) == 0 { return &fileDocument{ owner: owner, root: make(map[string]interface{}), dirty: false, }, nil } var doc map[string]interface{} err = json.Unmarshal(bt, &doc) if err != nil { return nil, err } return &fileDocument{ owner: owner, root: doc, dirty: false, }, nil } func findNodeInterface(doc map[string]interface{}, path string) (interface{}, error) { if doc == nil { return nil, ErrDocumentNotExist } parent := doc for { idx := strings.IndexRune(path, '/') var nodename string if idx < 0 { nodename = path path = "" } else { nodename = path[:idx] path = path[idx+1:] } child, ok := parent[nodename] if !ok { return nil, ErrDocumentPathNotExist } if len(path) == 0 { return child, nil } if parent, ok = child.(map[string]interface{}); !ok { return nil, ErrDocumentPathTypeMismatch } } } func findEdgeContainer(doc map[string]interface{}, path string) (map[string]interface{}, string) { parent := doc for { idx := strings.IndexRune(path, '/') var nodename string if idx < 0 { nodename = path path = "" } else { nodename = path[:idx] path = path[idx+1:] } if len(path) == 0 { return parent, nodename } child, ok := parent[nodename] if !ok { child = make(map[string]interface{}) parent[nodename] = child } parent = child.(map[string]interface{}) } } // ReadBool : func (doc *fileDocument) ReadBool(path string) (bool, error) { val, err := findNodeInterface(doc.root, path) if err != nil { return false, err } out, ok := val.(bool) if !ok { return false, ErrDocumentPathTypeMismatch } return out, nil } // ReadString : func (doc *fileDocument) ReadString(path string) (string, error) { val, err := findNodeInterface(doc.root, path) if err != nil { return "", err } out, ok := val.(string) if !ok { return "", ErrDocumentPathTypeMismatch } return out, nil } // Read : func (doc *fileDocument) Read(path string) (interface{}, error) { return findNodeInterface(doc.root, path) } // WriteBool : func (doc *fileDocument) Write(path string, val interface{}) { container, edge := findEdgeContainer(doc.root, path) container[edge] = val doc.dirty = true } // Serialize : func (doc *fileDocument) Serialize() error { if !doc.dirty { return nil } bt, err := json.Marshal(doc.root) if err != nil { return err } filepath := getDocumentFilepath(doc.owner) if err := os.WriteFile(filepath, bt, 0644); err != nil { if _, patherr := err.(*os.PathError); !patherr { return err } dir := path.Dir(filepath) if err = os.MkdirAll(dir, 0755); err != nil { return err } if err = os.WriteFile(filepath, bt, 0644); err != nil { return err } } doc.dirty = false return nil }