rewrote config file path search (#219)
added comment to clarify coding choices added package xdg to vendor folder rewrote config file path search Co-authored-by: crapStone <crapstone01@gmail.com> Reviewed-on: https://gitea.com/gitea/tea/pulls/219 Reviewed-by: 6543 <6543@noreply.gitea.io> Reviewed-by: Norwin <noerw@noreply.gitea.io>
This commit is contained in:
parent
cbd1bccbf9
commit
c4e2db32b5
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
code.gitea.io/gitea-vet v0.2.0
|
code.gitea.io/gitea-vet v0.2.0
|
||||||
code.gitea.io/sdk/gitea v0.13.0
|
code.gitea.io/sdk/gitea v0.13.0
|
||||||
github.com/AlecAivazis/survey/v2 v2.1.1
|
github.com/AlecAivazis/survey/v2 v2.1.1
|
||||||
|
github.com/adrg/xdg v0.2.1
|
||||||
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1
|
github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1
|
||||||
github.com/charmbracelet/glamour v0.2.0
|
github.com/charmbracelet/glamour v0.2.0
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -7,6 +7,8 @@ github.com/AlecAivazis/survey/v2 v2.1.1/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0T
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
|
||||||
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
|
||||||
|
github.com/adrg/xdg v0.2.1 h1:VSVdnH7cQ7V+B33qSJHTCRlNgra1607Q8PzEmnvb2Ic=
|
||||||
|
github.com/adrg/xdg v0.2.1/go.mod h1:ZuOshBmzV4Ta+s23hdfFZnBsdzmoR3US0d7ErpqSbTQ=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
|
|
|
@ -9,13 +9,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/tea/modules/git"
|
"code.gitea.io/tea/modules/git"
|
||||||
"code.gitea.io/tea/modules/utils"
|
"code.gitea.io/tea/modules/utils"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,29 +26,34 @@ type LocalConfig struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Config contain if loaded local tea config
|
// Config contain if loaded local tea config
|
||||||
Config LocalConfig
|
Config LocalConfig
|
||||||
yamlConfigPath string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: do not use init function to detect the tea configuration, use GetConfigPath()
|
|
||||||
func init() {
|
|
||||||
homeDir, err := utils.Home()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Retrieve home dir failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir := filepath.Join(homeDir, ".tea")
|
|
||||||
err = os.MkdirAll(dir, os.ModePerm)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Init tea config dir " + dir + " failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
yamlConfigPath = filepath.Join(dir, "tea.yml")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfigPath return path to tea config file
|
// GetConfigPath return path to tea config file
|
||||||
func GetConfigPath() string {
|
func GetConfigPath() string {
|
||||||
return yamlConfigPath
|
configFilePath, err := xdg.ConfigFile("tea/config.yml")
|
||||||
|
|
||||||
|
var exists bool
|
||||||
|
if err != nil {
|
||||||
|
exists = false
|
||||||
|
} else {
|
||||||
|
exists, _ = utils.PathExists(configFilePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to old config if no new one exists
|
||||||
|
if !exists {
|
||||||
|
file := filepath.Join(xdg.Home, ".tea", "tea.yml")
|
||||||
|
exists, _ = utils.PathExists(file)
|
||||||
|
if exists {
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unable to get or create config file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return configFilePath
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig load config into global Config var
|
// LoadConfig load config into global Config var
|
||||||
|
@ -58,12 +63,12 @@ func LoadConfig() error {
|
||||||
if exist {
|
if exist {
|
||||||
bs, err := ioutil.ReadFile(ymlPath)
|
bs, err := ioutil.ReadFile(ymlPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Failed to read config file: %s", ymlPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = yaml.Unmarshal(bs, &Config)
|
err = yaml.Unmarshal(bs, &Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Failed to parse contents of config file: %s", ymlPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -131,7 +131,7 @@ func AddLogin(name, token, user, passwd, sshKey, giteaURL string, insecure bool)
|
||||||
|
|
||||||
err := LoadConfig()
|
err := LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Unable to load config file " + yamlConfigPath)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range Config.Logins {
|
for _, l := range Config.Logins {
|
||||||
|
@ -253,7 +253,7 @@ func InitCommand(repoValue, loginValue, remoteValue string) (*Login, string, str
|
||||||
|
|
||||||
err := LoadConfig()
|
err := LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("load config file failed ", yamlConfigPath)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if login, err = GetDefaultLogin(); err != nil {
|
if login, err = GetDefaultLogin(); err != nil {
|
||||||
|
@ -287,7 +287,7 @@ func InitCommand(repoValue, loginValue, remoteValue string) (*Login, string, str
|
||||||
func InitCommandLoginOnly(loginValue string) *Login {
|
func InitCommandLoginOnly(loginValue string) *Login {
|
||||||
err := LoadConfig()
|
err := LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("load config file failed ", yamlConfigPath)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var login *Login
|
var login *Login
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"os/user"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Home returns the home directory for the executing user.
|
|
||||||
//
|
|
||||||
// This uses an OS-specific method for discovering the home directory.
|
|
||||||
// An error is returned if a home directory cannot be detected.
|
|
||||||
func Home() (string, error) {
|
|
||||||
user, err := user.Current()
|
|
||||||
if nil == err {
|
|
||||||
return user.HomeDir, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cross compile support
|
|
||||||
if "windows" == runtime.GOOS {
|
|
||||||
return homeWindows()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unix-like system, so just assume Unix
|
|
||||||
return homeUnix()
|
|
||||||
}
|
|
||||||
|
|
||||||
func homeUnix() (string, error) {
|
|
||||||
// First prefer the HOME environmental variable
|
|
||||||
if home := os.Getenv("HOME"); home != "" {
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If that fails, try getent
|
|
||||||
var stdout bytes.Buffer
|
|
||||||
cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid()))
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
// If the error is ErrNotFound, we ignore it. Otherwise, return it.
|
|
||||||
if err != exec.ErrNotFound {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if passwd := strings.TrimSpace(stdout.String()); passwd != "" {
|
|
||||||
// username:password:uid:gid:gecos:home:shell
|
|
||||||
passwdParts := strings.SplitN(passwd, ":", 7)
|
|
||||||
if len(passwdParts) > 5 {
|
|
||||||
return passwdParts[5], nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all else fails, try the shell
|
|
||||||
stdout.Reset()
|
|
||||||
cmd = exec.Command("sh", "-c", "cd && pwd")
|
|
||||||
cmd.Stdout = &stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := strings.TrimSpace(stdout.String())
|
|
||||||
if result == "" {
|
|
||||||
return "", errors.New("blank output when reading home directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func homeWindows() (string, error) {
|
|
||||||
// First prefer the HOME environmental variable
|
|
||||||
if home := os.Getenv("HOME"); home != "" {
|
|
||||||
return home, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
drive := os.Getenv("HOMEDRIVE")
|
|
||||||
path := os.Getenv("HOMEPATH")
|
|
||||||
home := drive + path
|
|
||||||
if drive == "" || path == "" {
|
|
||||||
home = os.Getenv("USERPROFILE")
|
|
||||||
}
|
|
||||||
if home == "" {
|
|
||||||
return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
return home, nil
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.11.x
|
||||||
|
- 1.12.x
|
||||||
|
- 1.13.x
|
||||||
|
os:
|
||||||
|
- linux
|
||||||
|
- osx
|
||||||
|
- windows
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
git:
|
||||||
|
autocrlf: false
|
||||||
|
before_install:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- go install github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
script:
|
||||||
|
- golangci-lint run --enable-all -D wsl -D gochecknoinits -D gochecknoglobals -D prealloc
|
||||||
|
- go test -v -race ./...
|
|
@ -0,0 +1,77 @@
|
||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as
|
||||||
|
contributors and maintainers pledge to making participation in our project and
|
||||||
|
our community a harassment-free experience for everyone, regardless of age,
|
||||||
|
body size, disability, ethnicity, sex characteristics, gender identity and
|
||||||
|
expression, level of experience, education, socio-economic status, nationality,
|
||||||
|
personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behaviour that contributes to creating a positive environment
|
||||||
|
include:
|
||||||
|
|
||||||
|
* Using welcoming and inclusive language
|
||||||
|
* Being respectful of differing viewpoints and experiences
|
||||||
|
* Gracefully accepting constructive criticism
|
||||||
|
* Focusing on what is best for the community
|
||||||
|
* Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behaviour by participants include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||||
|
advances
|
||||||
|
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or electronic
|
||||||
|
address, without explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable
|
||||||
|
behaviour and are expected to take appropriate and fair corrective action in
|
||||||
|
response to any instances of unacceptable behaviour.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or
|
||||||
|
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||||
|
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||||
|
permanently any contributor for other behaviour that they deem inappropriate,
|
||||||
|
threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies both within project spaces and in public spaces
|
||||||
|
when an individual is representing the project or its community. Examples of
|
||||||
|
representing a project or community include using an official project e-mail
|
||||||
|
address, posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event. Representation of a project may be
|
||||||
|
further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behaviour may be
|
||||||
|
reported by contacting the project team at adrg@epistack.com. All complaints
|
||||||
|
will be reviewed and investigated and will result in a response that is deemed
|
||||||
|
necessary and appropriate to the circumstances. The project team is obligated to
|
||||||
|
maintain confidentiality with regard to the reporter of an incident.
|
||||||
|
Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||||
|
faith may face temporary or permanent repercussions as determined by other
|
||||||
|
members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 1.4, available at
|
||||||
|
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see
|
||||||
|
https://www.contributor-covenant.org/faq
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Contributing to this project
|
||||||
|
|
||||||
|
Contributions in the form of pull requests, issues or just general feedback,
|
||||||
|
are always welcome. Please take a moment to review this document in order to
|
||||||
|
make the contribution process easy and effective for everyone involved.
|
||||||
|
|
||||||
|
Following these guidelines helps to communicate that you respect the time of
|
||||||
|
the developers managing and developing this open source project. In return,
|
||||||
|
they should reciprocate that respect in addressing your issue or assessing
|
||||||
|
patches and features.
|
||||||
|
|
||||||
|
## Using the issue tracker
|
||||||
|
|
||||||
|
The issue tracker is the preferred channel for [bug reports](#bugs),
|
||||||
|
[features requests](#features) and [submitting pull
|
||||||
|
requests](#pull-requests), but please respect the following restrictions:
|
||||||
|
|
||||||
|
* Please **do not** use the issue tracker for personal support requests (use
|
||||||
|
[Stack Overflow](http://stackoverflow.com) or IRC).
|
||||||
|
* Please **do not** derail or troll issues. Keep the discussion on topic and
|
||||||
|
respect the opinions of others.
|
||||||
|
|
||||||
|
<a name="bugs"></a>
|
||||||
|
## Bug reports
|
||||||
|
|
||||||
|
A bug is a _demonstrable problem_ that is caused by the code in the repository.
|
||||||
|
Good bug reports are extremely helpful - thank you!
|
||||||
|
|
||||||
|
Guidelines for bug reports:
|
||||||
|
|
||||||
|
1. **Use the GitHub issue search** — check if the issue has already been
|
||||||
|
reported.
|
||||||
|
2. **Check if the issue has been fixed** — try to reproduce it using the
|
||||||
|
latest `master` or development branch in the repository.
|
||||||
|
3. **Isolate the problem** — create a reduced test case.
|
||||||
|
|
||||||
|
A good bug report shouldn't leave others needing to chase you up for more
|
||||||
|
information. Please try to be as detailed as possible in your report. What is
|
||||||
|
your environment? What steps will reproduce the issue? What browser(s) and OS
|
||||||
|
experience the problem? What would you expect to be the outcome? All these
|
||||||
|
details will help people to fix any potential bugs.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
> Short and descriptive example bug report title
|
||||||
|
>
|
||||||
|
> A summary of the issue and the browser/OS environment in which it occurs. If
|
||||||
|
> suitable, include the steps required to reproduce the bug.
|
||||||
|
>
|
||||||
|
> 1. This is the first step
|
||||||
|
> 2. This is the second step
|
||||||
|
> 3. Further steps, etc.
|
||||||
|
>
|
||||||
|
> `<url>` - a link to the reduced test case
|
||||||
|
>
|
||||||
|
> Any other information you want to share that is relevant to the issue being
|
||||||
|
> reported. This might include the lines of code that you have identified as
|
||||||
|
> causing the bug, and potential solutions (and your opinions on their
|
||||||
|
> merits).
|
||||||
|
|
||||||
|
|
||||||
|
<a name="features"></a>
|
||||||
|
## Feature requests
|
||||||
|
|
||||||
|
Feature requests are welcome. But take a moment to find out whether your idea
|
||||||
|
fits with the scope and aims of the project. It's up to *you* to make a strong
|
||||||
|
case to convince the project's developers of the merits of this feature. Please
|
||||||
|
provide as much detail and context as possible.
|
||||||
|
|
||||||
|
|
||||||
|
<a name="pull-requests"></a>
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
Good pull requests - patches, improvements, new features - are a fantastic
|
||||||
|
help. They should remain focused in scope and avoid containing unrelated
|
||||||
|
commits.
|
||||||
|
|
||||||
|
**Please ask first** before embarking on any significant pull request (e.g.
|
||||||
|
implementing features, refactoring code, porting to a different language),
|
||||||
|
otherwise you risk spending a lot of time working on something that the
|
||||||
|
project's developers might not want to merge into the project.
|
||||||
|
|
||||||
|
Please adhere to the coding conventions used throughout a project (indentation,
|
||||||
|
accurate comments, etc.) and any other requirements (such as test coverage).
|
||||||
|
|
||||||
|
Follow this process if you'd like your work considered for inclusion in the
|
||||||
|
project:
|
||||||
|
|
||||||
|
1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
|
||||||
|
and configure the remotes:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone your fork of the repo into the current directory
|
||||||
|
git clone https://github.com/<your-username>/<repo-name>
|
||||||
|
# Navigate to the newly cloned directory
|
||||||
|
cd <repo-name>
|
||||||
|
# Assign the original repo to a remote called "upstream"
|
||||||
|
git remote add upstream https://github.com/<upstream-owner>/<repo-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. If you cloned a while ago, get the latest changes from upstream:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout <dev-branch>
|
||||||
|
git pull upstream <dev-branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Create a new topic branch (off the main project development branch) to
|
||||||
|
contain your feature, change, or fix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout -b <topic-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Commit your changes in logical chunks and use descriptive commit messages.
|
||||||
|
Use [interactive rebase](https://help.github.com/articles/interactive-rebase)
|
||||||
|
to tidy up your commits before making them public.
|
||||||
|
|
||||||
|
5. Locally merge (or rebase) the upstream development branch into your topic branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git pull [--rebase] upstream <dev-branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Push your topic branch up to your fork:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git push origin <topic-branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
|
||||||
|
with a clear title and description.
|
||||||
|
|
||||||
|
**IMPORTANT**: By submitting a patch, you agree to allow the project owner to
|
||||||
|
license your work under the same license as that used by the project.
|
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Adrian-George Bostan <adrg@epistack.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
|
@ -0,0 +1,187 @@
|
||||||
|
xdg
|
||||||
|
===
|
||||||
|
[![Build Status](https://travis-ci.org/adrg/xdg.svg?branch=master)](https://travis-ci.org/adrg/xdg)
|
||||||
|
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/adrg/xdg)
|
||||||
|
[![License: MIT](https://img.shields.io/badge/license-MIT-red.svg?style=flat-square)](https://opensource.org/licenses/MIT)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/adrg/xdg)](https://goreportcard.com/report/github.com/adrg/xdg)
|
||||||
|
|
||||||
|
Provides an implementation of the [XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html).
|
||||||
|
The specification defines a set of standard paths for storing application files,
|
||||||
|
including data and configuration files. For portability and flexibility reasons,
|
||||||
|
applications should use the XDG defined locations instead of hardcoding paths.
|
||||||
|
The package also includes the locations of well known user directories.
|
||||||
|
The current implementation supports Windows, Mac OS and most flavors of Unix.
|
||||||
|
|
||||||
|
Full documentation can be found at: https://godoc.org/github.com/adrg/xdg
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
go get github.com/adrg/xdg
|
||||||
|
|
||||||
|
## Default locations
|
||||||
|
|
||||||
|
The package defines sensible defaults for XDG variables which are empty or not
|
||||||
|
present in the environment.
|
||||||
|
|
||||||
|
#### XDG Base Directory
|
||||||
|
|
||||||
|
| | Unix | Mac OS | Windows |
|
||||||
|
| :--- | :--- | :----- | :--- |
|
||||||
|
| XDG_DATA_HOME | `~/.local/share` | `~/Library/Application Support` | `%LOCALAPPDATA%` |
|
||||||
|
| XDG_DATA_DIRS | `/usr/local/share`<br/>`/usr/share` | `/Library/Application Support` | `%APPDATA%\Roaming`<br/>`%PROGRAMDATA%` |
|
||||||
|
| XDG_CONFIG_HOME | `~/.config` | `~/Library/Preferences` | `%LOCALAPPDATA%` |
|
||||||
|
| XDG_CONFIG_DIRS | `/etc/xdg` | `/Library/Preferences` | `%PROGRAMDATA%` |
|
||||||
|
| XDG_CACHE_HOME | `~/.cache` | `~/Library/Caches` | `%LOCALAPPDATA%\cache` |
|
||||||
|
| XDG_RUNTIME_DIR | `/run/user/UID` | `~/Library/Application Support` | `%LOCALAPPDATA%` |
|
||||||
|
|
||||||
|
#### XDG user directories
|
||||||
|
|
||||||
|
| | Unix | Mac OS | Windows |
|
||||||
|
| :--- | :--- | :----- | :--- |
|
||||||
|
| XDG_DESKTOP_DIR | `~/Desktop` | `~/Desktop` | `%USERPROFILE%/Desktop` |
|
||||||
|
| XDG_DOWNLOAD_DIR | `~/Downloads` | `~/Downloads` | `%USERPROFILE%/Downloads` |
|
||||||
|
| XDG_DOCUMENTS_DIR | `~/Documents` | `~/Documents` | `%USERPROFILE%/Documents` |
|
||||||
|
| XDG_MUSIC_DIR | `~/Music` | `~/Music` | `%USERPROFILE%/Music` |
|
||||||
|
| XDG_PICTURES_DIR | `~/Pictures` | `~/Pictures` | `%USERPROFILE%/Pictures` |
|
||||||
|
| XDG_VIDEOS_DIR | `~/Videos` | `~/Movies` | `%USERPROFILE%/Videos` |
|
||||||
|
| XDG_TEMPLATES_DIR | `~/Templates` | `~/Templates` | `%USERPROFILE%/Templates` |
|
||||||
|
| XDG_PUBLICSHARE_DIR | `~/Public` | `~/Public` | `%PUBLIC%` |
|
||||||
|
|
||||||
|
#### Non-standard directories
|
||||||
|
|
||||||
|
Application directories
|
||||||
|
|
||||||
|
```
|
||||||
|
Unix:
|
||||||
|
- $XDG_DATA_HOME/applications
|
||||||
|
- ~/.local/share/applications
|
||||||
|
- /usr/local/share/applications
|
||||||
|
- /usr/share/applications
|
||||||
|
- $XDG_DATA_DIRS/applications
|
||||||
|
|
||||||
|
Mac OS:
|
||||||
|
- /Applications
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
- %APPDATA%\Roaming\Microsoft\Windows\Start Menu\Programs
|
||||||
|
```
|
||||||
|
|
||||||
|
Font Directories
|
||||||
|
|
||||||
|
```
|
||||||
|
Unix:
|
||||||
|
- $XDG_DATA_HOME/fonts
|
||||||
|
- ~/.fonts
|
||||||
|
- ~/.local/share/fonts
|
||||||
|
- /usr/local/share/fonts
|
||||||
|
- /usr/share/fonts
|
||||||
|
- $XDG_DATA_DIRS/fonts
|
||||||
|
|
||||||
|
Mac OS:
|
||||||
|
- ~/Library/Fonts
|
||||||
|
- /Library/Fonts
|
||||||
|
- /System/Library/Fonts
|
||||||
|
- /Network/Library/Fonts
|
||||||
|
|
||||||
|
Windows:
|
||||||
|
- %windir%\Fonts
|
||||||
|
- %LOCALAPPDATA%\Microsoft\Windows\Fonts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
#### XDG Base Directory
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// XDG Base Directory paths.
|
||||||
|
log.Println("Home config directory:", xdg.DataHome)
|
||||||
|
log.Println("Data directories:", xdg.DataDirs)
|
||||||
|
log.Println("Home config directory:", xdg.ConfigHome)
|
||||||
|
log.Println("Config directories:", xdg.ConfigDirs)
|
||||||
|
log.Println("Cache directory:", xdg.CacheHome)
|
||||||
|
log.Println("Runtime directory:", xdg.RuntimeDir)
|
||||||
|
|
||||||
|
// Non-standard directories.
|
||||||
|
log.Println("Application directories:", xdg.ApplicationDirs)
|
||||||
|
log.Println("Font directories:", xdg.FontDirs)
|
||||||
|
|
||||||
|
// Obtain a suitable location for application config files.
|
||||||
|
// ConfigFile takes one parameter which must contain the name of the file,
|
||||||
|
// but it can also contain a set of parent directories. If the directories
|
||||||
|
// don't exists, they will be created relative to the base config directory.
|
||||||
|
configFilePath, err := xdg.ConfigFile("appname/config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Save the config file at:", configFilePath)
|
||||||
|
|
||||||
|
// For other types of application files use:
|
||||||
|
// xdg.DataFile()
|
||||||
|
// xdg.CacheFile()
|
||||||
|
// xdg.RuntimeFile()
|
||||||
|
|
||||||
|
// Finding application config files.
|
||||||
|
// SearchConfigFile takes one parameter which must contain the name of
|
||||||
|
// the file, but it can also contain a set of parent directories relative
|
||||||
|
// to the config search paths (xdg.ConfigHome and xdg.ConfigDirs).
|
||||||
|
configFilePath, err = xdg.SearchConfigFile("appname/config.yaml")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
log.Println("Config file was found at:", configFilePath)
|
||||||
|
|
||||||
|
// For other types of application files use:
|
||||||
|
// xdg.SearchDataFile()
|
||||||
|
// xdg.SearchCacheFile()
|
||||||
|
// xdg.SearchRuntimeFile()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### XDG user directories
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/adrg/xdg"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// XDG user directories.
|
||||||
|
log.Println("Desktop directory:", xdg.UserDirs.Desktop)
|
||||||
|
log.Println("Download directory:", xdg.UserDirs.Download)
|
||||||
|
log.Println("Documents directory:", xdg.UserDirs.Documents)
|
||||||
|
log.Println("Music directory:", xdg.UserDirs.Music)
|
||||||
|
log.Println("Pictures directory:", xdg.UserDirs.Pictures)
|
||||||
|
log.Println("Videos directory:", xdg.UserDirs.Videos)
|
||||||
|
log.Println("Templates directory:", xdg.UserDirs.Templates)
|
||||||
|
log.Println("Public directory:", xdg.UserDirs.PublicShare)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## References
|
||||||
|
For more information see the
|
||||||
|
[XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and
|
||||||
|
[XDG user directories](https://wiki.archlinux.org/index.php/XDG_user_directories).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions in the form of pull requests, issues or just general feedback,
|
||||||
|
are always welcome.
|
||||||
|
See [CONTRIBUTING.MD](https://github.com/adrg/xdg/blob/master/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## License
|
||||||
|
Copyright (c) 2014 Adrian-George Bostan.
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](https://opensource.org/licenses/MIT).
|
||||||
|
See [LICENSE](https://github.com/adrg/xdg/blob/master/LICENSE) for more details.
|
|
@ -0,0 +1,78 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// XDG Base Directory environment variables.
|
||||||
|
var (
|
||||||
|
envDataHome = "XDG_DATA_HOME"
|
||||||
|
envDataDirs = "XDG_DATA_DIRS"
|
||||||
|
envConfigHome = "XDG_CONFIG_HOME"
|
||||||
|
envConfigDirs = "XDG_CONFIG_DIRS"
|
||||||
|
envCacheHome = "XDG_CACHE_HOME"
|
||||||
|
envRuntimeDir = "XDG_RUNTIME_DIR"
|
||||||
|
)
|
||||||
|
|
||||||
|
type baseDirectories struct {
|
||||||
|
dataHome string
|
||||||
|
data []string
|
||||||
|
configHome string
|
||||||
|
config []string
|
||||||
|
cacheHome string
|
||||||
|
runtime string
|
||||||
|
|
||||||
|
// Non-standard directories.
|
||||||
|
fonts []string
|
||||||
|
applications []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) dataFile(relPath string) (string, error) {
|
||||||
|
return createPath(relPath, append([]string{bd.dataHome}, bd.data...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) configFile(relPath string) (string, error) {
|
||||||
|
return createPath(relPath, append([]string{bd.configHome}, bd.config...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) cacheFile(relPath string) (string, error) {
|
||||||
|
return createPath(relPath, []string{bd.cacheHome})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) runtimeFile(relPath string) (string, error) {
|
||||||
|
fi, err := os.Lstat(bd.runtime)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return createPath(relPath, []string{bd.runtime})
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
|
// The runtime directory must be owned by the user.
|
||||||
|
if err = os.Chown(bd.runtime, os.Getuid(), os.Getgid()); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For security reasons, the runtime directory cannot be a symlink.
|
||||||
|
if err = os.Remove(bd.runtime); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPath(relPath, []string{bd.runtime})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) searchDataFile(relPath string) (string, error) {
|
||||||
|
return searchFile(relPath, append([]string{bd.dataHome}, bd.data...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) searchConfigFile(relPath string) (string, error) {
|
||||||
|
return searchFile(relPath, append([]string{bd.configHome}, bd.config...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) searchCacheFile(relPath string) (string, error) {
|
||||||
|
return searchFile(relPath, []string{bd.cacheHome})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bd baseDirectories) searchRuntimeFile(relPath string) (string, error) {
|
||||||
|
return searchFile(relPath, []string{bd.runtime})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
module github.com/adrg/xdg
|
|
@ -0,0 +1,37 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBaseDirs(home string) {
|
||||||
|
// Initialize base directories.
|
||||||
|
baseDirs.dataHome = xdgPath(envDataHome, filepath.Join(home, "Library", "Application Support"))
|
||||||
|
baseDirs.data = xdgPaths(envDataDirs, "/Library/Application Support")
|
||||||
|
baseDirs.configHome = xdgPath(envConfigHome, filepath.Join(home, "Library", "Preferences"))
|
||||||
|
baseDirs.config = xdgPaths(envConfigDirs, "/Library/Preferences")
|
||||||
|
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, "Library", "Caches"))
|
||||||
|
baseDirs.runtime = xdgPath(envRuntimeDir, filepath.Join(home, "Library", "Application Support"))
|
||||||
|
|
||||||
|
// Initialize non-standard directories.
|
||||||
|
baseDirs.applications = []string{
|
||||||
|
"/Applications",
|
||||||
|
}
|
||||||
|
baseDirs.fonts = []string{
|
||||||
|
filepath.Join(home, "Library/Fonts"),
|
||||||
|
"/Library/Fonts",
|
||||||
|
"/System/Library/Fonts",
|
||||||
|
"/Network/Library/Fonts",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUserDirs(home string) {
|
||||||
|
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop"))
|
||||||
|
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads"))
|
||||||
|
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents"))
|
||||||
|
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music"))
|
||||||
|
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures"))
|
||||||
|
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Movies"))
|
||||||
|
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates"))
|
||||||
|
UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public"))
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
// +build aix dragonfly freebsd linux nacl netbsd openbsd solaris
|
||||||
|
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBaseDirs(home string) {
|
||||||
|
// Initialize base directories.
|
||||||
|
baseDirs.dataHome = xdgPath(envDataHome, filepath.Join(home, ".local", "share"))
|
||||||
|
baseDirs.data = xdgPaths(envDataDirs, "/usr/local/share", "/usr/share")
|
||||||
|
baseDirs.configHome = xdgPath(envConfigHome, filepath.Join(home, ".config"))
|
||||||
|
baseDirs.config = xdgPaths(envConfigDirs, "/etc/xdg")
|
||||||
|
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(home, ".cache"))
|
||||||
|
baseDirs.runtime = xdgPath(envRuntimeDir, filepath.Join("/run/user", strconv.Itoa(os.Getuid())))
|
||||||
|
|
||||||
|
// Initialize non-standard directories.
|
||||||
|
appDirs := []string{
|
||||||
|
filepath.Join(baseDirs.dataHome, "applications"),
|
||||||
|
filepath.Join(home, ".local/share/applications"),
|
||||||
|
"/usr/local/share/applications",
|
||||||
|
"/usr/share/applications",
|
||||||
|
}
|
||||||
|
|
||||||
|
fontDirs := []string{
|
||||||
|
filepath.Join(baseDirs.dataHome, "fonts"),
|
||||||
|
filepath.Join(home, ".fonts"),
|
||||||
|
filepath.Join(home, ".local/share/fonts"),
|
||||||
|
"/usr/local/share/fonts",
|
||||||
|
"/usr/share/fonts",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, dir := range baseDirs.data {
|
||||||
|
appDirs = append(appDirs, filepath.Join(dir, "applications"))
|
||||||
|
fontDirs = append(fontDirs, filepath.Join(dir, "fonts"))
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDirs.applications = uniquePaths(appDirs)
|
||||||
|
baseDirs.fonts = uniquePaths(fontDirs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUserDirs(home string) {
|
||||||
|
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop"))
|
||||||
|
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads"))
|
||||||
|
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents"))
|
||||||
|
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music"))
|
||||||
|
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures"))
|
||||||
|
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Videos"))
|
||||||
|
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates"))
|
||||||
|
UserDirs.PublicShare = xdgPath(envPublicShareDir, filepath.Join(home, "Public"))
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initBaseDirs(home string) {
|
||||||
|
appDataDir := os.Getenv("APPDATA")
|
||||||
|
if appDataDir == "" {
|
||||||
|
appDataDir = filepath.Join(home, "AppData")
|
||||||
|
}
|
||||||
|
roamingAppDataDir := filepath.Join(appDataDir, "Roaming")
|
||||||
|
|
||||||
|
localAppDataDir := os.Getenv("LOCALAPPDATA")
|
||||||
|
if localAppDataDir == "" {
|
||||||
|
localAppDataDir = filepath.Join(appDataDir, "Local")
|
||||||
|
}
|
||||||
|
|
||||||
|
programDataDir := os.Getenv("PROGRAMDATA")
|
||||||
|
if programDataDir == "" {
|
||||||
|
if systemDrive := os.Getenv("SystemDrive"); systemDrive != "" {
|
||||||
|
programDataDir = filepath.Join(systemDrive, "ProgramData")
|
||||||
|
} else {
|
||||||
|
programDataDir = home
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
winDir := os.Getenv("windir")
|
||||||
|
if winDir == "" {
|
||||||
|
winDir = os.Getenv("SystemRoot")
|
||||||
|
if winDir == "" {
|
||||||
|
winDir = home
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize base directories.
|
||||||
|
baseDirs.dataHome = xdgPath(envDataHome, localAppDataDir)
|
||||||
|
baseDirs.data = xdgPaths(envDataDirs, roamingAppDataDir, programDataDir)
|
||||||
|
baseDirs.configHome = xdgPath(envConfigHome, localAppDataDir)
|
||||||
|
baseDirs.config = xdgPaths(envConfigDirs, programDataDir)
|
||||||
|
baseDirs.cacheHome = xdgPath(envCacheHome, filepath.Join(localAppDataDir, "cache"))
|
||||||
|
baseDirs.runtime = xdgPath(envRuntimeDir, localAppDataDir)
|
||||||
|
|
||||||
|
// Initialize non-standard directories.
|
||||||
|
baseDirs.applications = []string{
|
||||||
|
filepath.Join(roamingAppDataDir, "Microsoft", "Windows", "Start Menu", "Programs"),
|
||||||
|
}
|
||||||
|
baseDirs.fonts = []string{
|
||||||
|
filepath.Join(winDir, "Fonts"),
|
||||||
|
filepath.Join(localAppDataDir, "Microsoft", "Windows", "Fonts"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func initUserDirs(home string) {
|
||||||
|
publicDir := os.Getenv("PUBLIC")
|
||||||
|
if publicDir == "" {
|
||||||
|
publicDir = filepath.Join(home, "Public")
|
||||||
|
}
|
||||||
|
|
||||||
|
UserDirs.Desktop = xdgPath(envDesktopDir, filepath.Join(home, "Desktop"))
|
||||||
|
UserDirs.Download = xdgPath(envDownloadDir, filepath.Join(home, "Downloads"))
|
||||||
|
UserDirs.Documents = xdgPath(envDocumentsDir, filepath.Join(home, "Documents"))
|
||||||
|
UserDirs.Music = xdgPath(envMusicDir, filepath.Join(home, "Music"))
|
||||||
|
UserDirs.Pictures = xdgPath(envPicturesDir, filepath.Join(home, "Pictures"))
|
||||||
|
UserDirs.Videos = xdgPath(envVideosDir, filepath.Join(home, "Videos"))
|
||||||
|
UserDirs.Templates = xdgPath(envTemplatesDir, filepath.Join(home, "Templates"))
|
||||||
|
UserDirs.PublicShare = xdgPath(envPublicShareDir, publicDir)
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
// XDG user directories environment variables.
|
||||||
|
var (
|
||||||
|
envDesktopDir = "XDG_DESKTOP_DIR"
|
||||||
|
envDownloadDir = "XDG_DOWNLOAD_DIR"
|
||||||
|
envDocumentsDir = "XDG_DOCUMENTS_DIR"
|
||||||
|
envMusicDir = "XDG_MUSIC_DIR"
|
||||||
|
envPicturesDir = "XDG_PICTURES_DIR"
|
||||||
|
envVideosDir = "XDG_VIDEOS_DIR"
|
||||||
|
envTemplatesDir = "XDG_TEMPLATES_DIR"
|
||||||
|
envPublicShareDir = "XDG_PUBLICSHARE_DIR"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserDirectories defines the locations of well known user directories.
|
||||||
|
type UserDirectories struct {
|
||||||
|
// Desktop defines the location of the user's desktop directory.
|
||||||
|
Desktop string
|
||||||
|
|
||||||
|
// Download defines a suitable location for user downloaded files.
|
||||||
|
Download string
|
||||||
|
|
||||||
|
// Documents defines a suitable location for user document files.
|
||||||
|
Documents string
|
||||||
|
|
||||||
|
// Music defines a suitable location for user audio files.
|
||||||
|
Music string
|
||||||
|
|
||||||
|
// Pictures defines a suitable location for user image files.
|
||||||
|
Pictures string
|
||||||
|
|
||||||
|
// VideosDir defines a suitable location for user video files.
|
||||||
|
Videos string
|
||||||
|
|
||||||
|
// Templates defines a suitable location for user template files.
|
||||||
|
Templates string
|
||||||
|
|
||||||
|
// PublicShare defines a suitable location for user shared files.
|
||||||
|
PublicShare string
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func homeDir() string {
|
||||||
|
homeEnv := "HOME"
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
homeEnv = "USERPROFILE"
|
||||||
|
case "plan9":
|
||||||
|
homeEnv = "home"
|
||||||
|
}
|
||||||
|
|
||||||
|
if home := os.Getenv(homeEnv); home != "" {
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "nacl":
|
||||||
|
return "/"
|
||||||
|
case "darwin":
|
||||||
|
if runtime.GOARCH == "arm" || runtime.GOARCH == "arm64" {
|
||||||
|
return "/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(path string) bool {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
return err == nil || os.IsExist(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandPath(path, homeDir string) string {
|
||||||
|
if path == "" || homeDir == "" {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if path[0] == '~' {
|
||||||
|
return filepath.Join(homeDir, path[1:])
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(path, "$HOME") {
|
||||||
|
return filepath.Join(homeDir, path[5:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPath(name string, paths []string) (string, error) {
|
||||||
|
var searchedPaths []string
|
||||||
|
for _, p := range paths {
|
||||||
|
path := filepath.Join(p, name)
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
|
if exists(dir) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(dir, os.ModeDir|0700); err == nil {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedPaths = append(searchedPaths, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not create any of the following paths: %s",
|
||||||
|
strings.Join(searchedPaths, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchFile(name string, paths []string) (string, error) {
|
||||||
|
var searchedPaths []string
|
||||||
|
for _, p := range paths {
|
||||||
|
path := filepath.Join(p, name)
|
||||||
|
if exists(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
searchedPaths = append(searchedPaths, filepath.Dir(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("could not locate `%s` in any of the following paths: %s",
|
||||||
|
filepath.Base(name), strings.Join(searchedPaths, ", "))
|
||||||
|
}
|
||||||
|
|
||||||
|
func xdgPath(name, defaultPath string) string {
|
||||||
|
dir := expandPath(os.Getenv(name), Home)
|
||||||
|
if dir != "" && filepath.IsAbs(dir) {
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func xdgPaths(name string, defaultPaths ...string) []string {
|
||||||
|
dirs := uniquePaths(filepath.SplitList(os.Getenv(name)))
|
||||||
|
if len(dirs) != 0 {
|
||||||
|
return dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniquePaths(defaultPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniquePaths(paths []string) []string {
|
||||||
|
var uniq []string
|
||||||
|
registry := map[string]struct{}{}
|
||||||
|
|
||||||
|
for _, p := range paths {
|
||||||
|
dir := expandPath(p, Home)
|
||||||
|
if dir == "" || !filepath.IsAbs(dir) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := registry[dir]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
registry[dir] = struct{}{}
|
||||||
|
uniq = append(uniq, dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uniq
|
||||||
|
}
|
|
@ -0,0 +1,180 @@
|
||||||
|
/*
|
||||||
|
Package xdg provides an implementation of the XDG Base Directory
|
||||||
|
Specification. The specification defines a set of standard paths for storing
|
||||||
|
application files including data and configuration files. For portability and
|
||||||
|
flexibility reasons, applications should use the XDG defined locations instead
|
||||||
|
of hardcoding paths. The package also includes the locations of well known user
|
||||||
|
directories. The current implementation supports Windows, Mac OS and most
|
||||||
|
flavors of Unix.
|
||||||
|
|
||||||
|
For more information regarding the XDG Base Directory Specification see:
|
||||||
|
https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
|
||||||
|
|
||||||
|
For more information regarding the XDG user directories see:
|
||||||
|
https://wiki.archlinux.org/index.php/XDG_user_directories
|
||||||
|
*/
|
||||||
|
package xdg
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Home contains the path of the user's home directory.
|
||||||
|
Home string
|
||||||
|
|
||||||
|
// DataHome defines the base directory relative to which user-specific
|
||||||
|
// data files should be stored. This directory is defined by the
|
||||||
|
// environment variable $XDG_DATA_HOME. If this variable is not set,
|
||||||
|
// a default equal to $HOME/.local/share should be used.
|
||||||
|
DataHome string
|
||||||
|
|
||||||
|
// DataDirs defines the preference-ordered set of base directories to
|
||||||
|
// search for data files in addition to the DataHome base directory.
|
||||||
|
// This set of directories is defined by the environment variable
|
||||||
|
// $XDG_DATA_DIRS. If this variable is not set, the default directories
|
||||||
|
// to be used are /usr/local/share and /usr/share, in that order. The
|
||||||
|
// DataHome directory is considered more important than any of the
|
||||||
|
// directories defined by DataDirs. Therefore, user data files should be
|
||||||
|
// written relative to the DataHome directory, if possible.
|
||||||
|
DataDirs []string
|
||||||
|
|
||||||
|
// ConfigHome defines the base directory relative to which user-specific
|
||||||
|
// configuration files should be written. This directory is defined by
|
||||||
|
// the environment variable $XDG_CONFIG_HOME. If this variable is not
|
||||||
|
// not set, a default equal to $HOME/.config should be used.
|
||||||
|
ConfigHome string
|
||||||
|
|
||||||
|
// ConfigDirs defines the preference-ordered set of base directories to
|
||||||
|
// search for configuration files in addition to the ConfigHome base
|
||||||
|
// directory. This set of directories is defined by the environment
|
||||||
|
// variable $XDG_CONFIG_DIRS. If this variable is not set, a default
|
||||||
|
// equal to /etc/xdg should be used. The ConfigHome directory is
|
||||||
|
// considered more important than any of the directories defined by
|
||||||
|
// ConfigDirs. Therefore, user config files should be written
|
||||||
|
// relative to the ConfigHome directory, if possible.
|
||||||
|
ConfigDirs []string
|
||||||
|
|
||||||
|
// CacheHome defines the base directory relative to which user-specific
|
||||||
|
// non-essential (cached) data should be written. This directory is
|
||||||
|
// defined by the environment variable $XDG_CACHE_HOME. If this variable
|
||||||
|
// is not set, a default equal to $HOME/.cache should be used.
|
||||||
|
CacheHome string
|
||||||
|
|
||||||
|
// RuntimeDir defines the base directory relative to which user-specific
|
||||||
|
// non-essential runtime files and other file objects (such as sockets,
|
||||||
|
// named pipes, etc.) should be stored. This directory is defined by the
|
||||||
|
// environment variable $XDG_RUNTIME_DIR. If this variable is not set,
|
||||||
|
// applications should fall back to a replacement directory with similar
|
||||||
|
// capabilities. Applications should use this directory for communication
|
||||||
|
// and synchronization purposes and should not place larger files in it,
|
||||||
|
// since it might reside in runtime memory and cannot necessarily be
|
||||||
|
// swapped out to disk.
|
||||||
|
RuntimeDir string
|
||||||
|
|
||||||
|
// UserDirs defines the locations of well known user directories.
|
||||||
|
UserDirs UserDirectories
|
||||||
|
|
||||||
|
// FontDirs defines the common locations where font files are stored.
|
||||||
|
FontDirs []string
|
||||||
|
|
||||||
|
// ApplicationDirs defines the common locations of applications.
|
||||||
|
ApplicationDirs []string
|
||||||
|
|
||||||
|
// baseDirs defines the locations of base directories.
|
||||||
|
baseDirs baseDirectories
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reload refreshes base and user directories by reading the environment.
|
||||||
|
// Defaults are applied for XDG variables which are empty or not present
|
||||||
|
// in the environment.
|
||||||
|
func Reload() {
|
||||||
|
// Initialize home directory.
|
||||||
|
Home = homeDir()
|
||||||
|
|
||||||
|
// Initialize base directories.
|
||||||
|
initBaseDirs(Home)
|
||||||
|
DataHome = baseDirs.dataHome
|
||||||
|
DataDirs = baseDirs.data
|
||||||
|
ConfigHome = baseDirs.configHome
|
||||||
|
ConfigDirs = baseDirs.config
|
||||||
|
CacheHome = baseDirs.cacheHome
|
||||||
|
RuntimeDir = baseDirs.runtime
|
||||||
|
FontDirs = baseDirs.fonts
|
||||||
|
ApplicationDirs = baseDirs.applications
|
||||||
|
|
||||||
|
// Initialize user directories.
|
||||||
|
initUserDirs(Home)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataFile returns a suitable location for the specified data file.
|
||||||
|
// The relPath parameter must contain the name of the data file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.data).
|
||||||
|
// If the specified directories do not exist, they will be created relative
|
||||||
|
// to the base data directory. On failure, an error containing the
|
||||||
|
// attempted paths is returned.
|
||||||
|
func DataFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.dataFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigFile returns a suitable location for the specified config file.
|
||||||
|
// The relPath parameter must contain the name of the config file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.yaml).
|
||||||
|
// If the specified directories do not exist, they will be created relative
|
||||||
|
// to the base config directory. On failure, an error containing the
|
||||||
|
// attempted paths is returned.
|
||||||
|
func ConfigFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.configFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheFile returns a suitable location for the specified cache file.
|
||||||
|
// The relPath parameter must contain the name of the cache file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.cache).
|
||||||
|
// If the specified directories do not exist, they will be created relative
|
||||||
|
// to the base cache directory. On failure, an error containing the
|
||||||
|
// attempted paths is returned.
|
||||||
|
func CacheFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.cacheFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeFile returns a suitable location for the specified runtime file.
|
||||||
|
// The relPath parameter must contain the name of the runtime file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.pid).
|
||||||
|
// If the specified directories do not exist, they will be created relative
|
||||||
|
// to the base runtime directory. On failure, an error containing the
|
||||||
|
// attempted paths is returned.
|
||||||
|
func RuntimeFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.runtimeFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchDataFile searches for specified file in the data search paths.
|
||||||
|
// The relPath parameter must contain the name of the data file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.data). If the
|
||||||
|
// file cannot be found, an error specifying the searched paths is returned.
|
||||||
|
func SearchDataFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.searchDataFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchConfigFile searches for the specified file in config search paths.
|
||||||
|
// The relPath parameter must contain the name of the config file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.yaml). If the
|
||||||
|
// file cannot be found, an error specifying the searched paths is returned.
|
||||||
|
func SearchConfigFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.searchConfigFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchCacheFile searches for the specified file in the cache search path.
|
||||||
|
// The relPath parameter must contain the name of the cache file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.cache). If the
|
||||||
|
// file cannot be found, an error specifying the searched path is returned.
|
||||||
|
func SearchCacheFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.searchCacheFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchRuntimeFile searches for the specified file in the runtime search path.
|
||||||
|
// The relPath parameter must contain the name of the runtime file, and
|
||||||
|
// optionally, a set of parent directories (e.g. appname/app.pid). If the
|
||||||
|
// file cannot be found, an error specifying the searched path is returned.
|
||||||
|
func SearchRuntimeFile(relPath string) (string, error) {
|
||||||
|
return baseDirs.searchRuntimeFile(relPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Reload()
|
||||||
|
}
|
|
@ -7,6 +7,8 @@ code.gitea.io/sdk/gitea
|
||||||
github.com/AlecAivazis/survey/v2
|
github.com/AlecAivazis/survey/v2
|
||||||
github.com/AlecAivazis/survey/v2/core
|
github.com/AlecAivazis/survey/v2/core
|
||||||
github.com/AlecAivazis/survey/v2/terminal
|
github.com/AlecAivazis/survey/v2/terminal
|
||||||
|
# github.com/adrg/xdg v0.2.1
|
||||||
|
github.com/adrg/xdg
|
||||||
# github.com/alecthomas/chroma v0.7.3
|
# github.com/alecthomas/chroma v0.7.3
|
||||||
github.com/alecthomas/chroma
|
github.com/alecthomas/chroma
|
||||||
github.com/alecthomas/chroma/formatters
|
github.com/alecthomas/chroma/formatters
|
||||||
|
|
Loading…
Reference in New Issue