Newversion (#8)

* split to files

* Delete tesing code

* Change gnuflags to go-flags

* Change gnuflags to go-flags

* fix search torrent

* fix search torrent

* Replace impromevents. Separator option

* Help modification. Readme update
This commit is contained in:
Alexey Kostin 2020-04-04 16:58:29 +03:00 committed by GitHub
parent 372e9fb928
commit d2682d2ad9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 505 additions and 1415 deletions

View File

@ -19,6 +19,15 @@ Feature:
> [!NOTE]
> \* This torrents will not be done (0%) and will need force rehash
>
> [!NOTE]
> \* If you migrate from windows to linux and use replace function attention that multiple flags -r processing one by one
> [!NOTE]
> \* If you migrate from windows to linux and yours torrent files saves to some place you must use flag --search with actual paths in yours system
> [!NOTE]
> \* If you migrate from windows to linux you may need to define path separathor with --sep flag
> [!NOTE]
>\*\* The calculation of the completed parts is based only on the priority of the files in torrent
@ -35,25 +44,35 @@ Help:
Help (from cmd or powerwhell)
```
C:\Users\user\Downloads> .\bt2qbt_v1.0_amd64.exe -h
Usage of C:\Users\user\Downloads\bt2qbt_v1.0_amd64.exe:
-c, --qconfig (= "C:\\Users\\user\\AppData\\Roaming\\qBittorrent\\qBittorrent.ini")
qBittorrent config files (for write tags)
-d, --destination (= "C:\\Users\\user\\AppData\\Local\\qBittorrent\\BT_backup\\")
Destination directory BT_backup (as default)
--replace (= "")
Replace paths.
Delimiter for replaces - ;
Delimiter for from/to - ,
Example: "D:\films,/home/user/films;\,/"
If you use path separator different from you system, declare it mannually
C:\Users\user\Downloads\bt2qbt_v1.3_amd64.exe [OPTIONS]
Application Options:
-s, --source= Source directory that contains resume.dat and torrents
files (default:
C:\Users\user\AppData\Roaming\uTorrent\)
-d, --destination= Destination directory BT_backup (as default) (default:
C:\Users\user\AppData\Local\qBittorrent\BT_backup\)
-c, --config= Source directory that contains resume.dat and torrents
files (default:
C:\Users\user\AppData\Roaming\qBittorrent\qBittorren-
t.ini)
--without-labels Do not export/import labels
--without-tags Do not export/import tags
-t, --search= Additional search path for torrents files
Example: --search='/mnt/olddisk/savedtorrents'
--search='/mnt/olddisk/workstorrents'
-r, --replace= Replace paths.
Delimiter for from/to is comma - ,
Example: -r "D:\films,/home/user/films" -r "\,/"
If you use path separator different from you system,
declare it mannually
--sep= Default path separator that will use in all paths
(default: \)
Help Options:
-h, --help Show this help message
-s, --source (= "C:\\Users\\user\\AppData\\Roaming\\uTorrent\\")
Source directory that contains resume.dat and torrents files
--without-labels (= false)
Do not export/import labels
--without-tags (= false)
Do not export/import tags
```
Usage examples:
@ -61,7 +80,7 @@ Usage examples:
- If you just run application, it will processing torrents from %APPDATA%\uTorrent\ to %LOCALAPPDATA%\qBittorrent\BT_BACKUP\
```
C:\Users\user\Downloads> .\bt2qbt_v1.0_amd64.exe
C:\Users\user\Downloads> .\bt2qbt_v1.3_amd64.exe
It will be performed processing from directory C:\Users\user\AppData\Roaming\uTorrent\ to directory C:\Users\user\AppData\Local\qBittorrent\BT_backup\
Check that the qBittorrent is turned off and the directory C:\Users\user\AppData\Local\qBittorrent\BT_backup\ and config C:\Users\user\AppData\Roaming\qBittorrent\qBittorrent.ini is backed up.
@ -77,7 +96,7 @@ Press Enter to exit
- Run application from cmd or powershell with keys, if you want change source dir or destination dir, or export/import behavior
```
C:\Users\user\Downloads> .\bt2qbt_v1.0_amd64.exe -s C:\Users\user\AppData\Roaming\BitTorrent\
C:\Users\user\Downloads> .\bt2qbt_v1.3_amd64.exe -s C:\Users\user\AppData\Roaming\BitTorrent\
It will be performed processing from directory C:\Users\user\AppData\Roaming\BitTorrent\ to directory C:\Users\user\AppData\Local\qBittorrent\BT_backup\
Check that the qBittorrent is turned off and the directory C:\Users\user\AppData\Local\qBittorrent\BT_backup\ is backed up.

543
bt2qbt.go
View File

@ -3,19 +3,20 @@ package main
import (
"bufio"
"bytes"
"crypto/sha1"
"encoding/hex"
"fmt"
"github.com/fatih/color"
"github.com/go-ini/ini"
"github.com/juju/gnuflag"
goflags "github.com/jessevdk/go-flags"
"github.com/rumanzo/bt2qbt/libtorrent"
"github.com/rumanzo/bt2qbt/replace"
"github.com/zeebo/bencode"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"runtime"
"runtime/debug"
"strconv"
@ -25,13 +26,14 @@ import (
)
type Flags struct {
bitDir, qBitDir, config string
withoutLabels, withoutTags bool
replace string
}
type Replace struct {
from, to string
BitDir string `short:"s" long:"source" description:"Source directory that contains resume.dat and torrents files"`
QBitDir string `short:"d" long:"destination" description:"Destination directory BT_backup (as default)"`
Config string `short:"c" long:"config" description:"Source directory that contains resume.dat and torrents files"`
WithoutLabels bool `long:"without-labels" description:"Do not export/import labels"`
WithoutTags bool `long:"without-tags" description:"Do not export/import tags"`
SearchPaths []string `short:"t" long:"search" description:"Additional search path for torrents files"`
Replaces []string `short:"r" long:"replace" description:"Replace paths.\n Delimiter for from/to is comma - ,\n Example: -r \"D:\\films,/home/user/films\" -r \"\\,/\"\n If you use path separator different from you system, declare it mannually"`
PathSeparator string `long:"sep" description:"Default path separator that will use in all paths"`
}
type Channels struct {
@ -40,70 +42,7 @@ type Channels struct {
boundedChannel chan bool
}
type NewTorrentStructure struct {
ActiveTime int64 `bencode:"active_time"`
AddedTime int64 `bencode:"added_time"`
AnnounceToDht int64 `bencode:"announce_to_dht"`
AnnounceToLsd int64 `bencode:"announce_to_lsd"`
AnnounceToTrackers int64 `bencode:"announce_to_trackers"`
AutoManaged int64 `bencode:"auto_managed"`
BannedPeers string `bencode:"banned_peers"`
BannedPeers6 string `bencode:"banned_peers6"`
Blockperpiece int64 `bencode:"blocks per piece"`
CompletedTime int64 `bencode:"completed_time"`
DownloadRateLimit int64 `bencode:"download_rate_limit"`
Filesizes [][]int64 `bencode:"file sizes"`
Fileformat string `bencode:"file-format"`
Fileversion int64 `bencode:"file-version"`
FilePriority []int `bencode:"file_priority"`
FinishedTime int64 `bencode:"finished_time"`
Infohash string `bencode:"info-hash"`
LastSeenComplete int64 `bencode:"last_seen_complete"`
Libtorrentversion string `bencode:"libtorrent-version"`
MaxConnections int64 `bencode:"max_connections"`
MaxUploads int64 `bencode:"max_uploads"`
NumDownloaded int64 `bencode:"num_downloaded"`
NumIncomplete int64 `bencode:"num_incomplete"`
MappedFiles []string `bencode:"mapped_files,omitempty"`
Paused int64 `bencode:"paused"`
Peers string `bencode:"peers"`
Peers6 string `bencode:"peers6"`
Pieces []byte `bencode:"pieces"`
QbthasRootFolder int64 `bencode:"qBt-hasRootFolder"`
Qbtcategory string `bencode:"qBt-category,omitempty"`
Qbtname string `bencode:"qBt-name"`
QbtqueuePosition int `bencode:"qBt-queuePosition"`
QbtratioLimit int64 `bencode:"qBt-ratioLimit"`
QbtsavePath string `bencode:"qBt-savePath"`
QbtseedStatus int64 `bencode:"qBt-seedStatus"`
QbtseedingTimeLimit int64 `bencode:"qBt-seedingTimeLimit"`
Qbttags []string `bencode:"qBt-tags"`
QbttempPathDisabled int64 `bencode:"qBt-tempPathDisabled"`
SavePath string `bencode:"save_path"`
SeedMode int64 `bencode:"seed_mode"`
SeedingTime int64 `bencode:"seeding_time"`
SequentialDownload int64 `bencode:"sequential_download"`
SuperSeeding int64 `bencode:"super_seeding"`
TotalDownloaded int64 `bencode:"total_downloaded"`
TotalUploaded int64 `bencode:"total_uploaded"`
Trackers [][]string `bencode:"trackers"`
UploadRateLimit int64 `bencode:"upload_rate_limit"`
Unfinished *[]interface{} `bencode:"unfinished,omitempty"`
withoutLabels bool
withoutTags bool
hasFiles bool
torrentFilePath string
torrentfile map[string]interface{}
path string
fileSizes int64
sizeAndPrio [][]int64
torrentFileList []string
numPieces int64
pieceLenght int64
replace []Replace
}
func encodetorrentfile(path string, newstructure *NewTorrentStructure) error {
func encodetorrentfile(path string, newstructure *libtorrent.NewTorrentStructure) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
os.Create(path)
}
@ -155,15 +94,6 @@ func decodetorrentfile(path string) (map[string]interface{}, error) {
return torrent, nil
}
func fmtime(path string) (mtime int64) {
if fi, err := os.Stat(path); err != nil {
return 0
} else {
mtime = fi.ModTime().Unix()
return
}
}
func copyfile(src string, dst string) error {
originalFile, err := os.Open(src)
if err != nil {
@ -184,219 +114,6 @@ func copyfile(src string, dst string) error {
return nil
}
func (newstructure *NewTorrentStructure) started(started int64) {
if started == 0 {
newstructure.Paused = 1
newstructure.AutoManaged = 0
newstructure.AnnounceToDht = 0
newstructure.AnnounceToLsd = 0
newstructure.AnnounceToTrackers = 0
} else {
newstructure.Paused = 0
newstructure.AutoManaged = 1
newstructure.AnnounceToDht = 1
newstructure.AnnounceToLsd = 1
newstructure.AnnounceToTrackers = 1
}
}
func (newstructure *NewTorrentStructure) ifcompletedon() {
if newstructure.CompletedTime != 0 {
newstructure.LastSeenComplete = time.Now().Unix()
} else {
newstructure.Unfinished = new([]interface{})
}
}
func (newstructure *NewTorrentStructure) iftags(labels interface{}) {
if newstructure.withoutTags == false && labels != nil {
for _, label := range labels.([]interface{}) {
if label != nil {
newstructure.Qbttags = append(newstructure.Qbttags, label.(string))
}
}
} else {
newstructure.Qbttags = []string{}
}
}
func (newstructure *NewTorrentStructure) iflabel(label interface{}) {
if newstructure.withoutLabels == false {
switch label.(type) {
case nil:
newstructure.Qbtcategory = ""
case string:
newstructure.Qbtcategory = label.(string)
}
} else {
newstructure.Qbtcategory = ""
}
}
func (newstructure *NewTorrentStructure) gettrackers(trackers interface{}) {
switch strct := trackers.(type) {
case []interface{}:
for _, st := range strct {
newstructure.gettrackers(st)
}
case string:
for _, str := range strings.Fields(strct) {
newstructure.Trackers = append(newstructure.Trackers, []string{str})
}
}
}
func (newstructure *NewTorrentStructure) prioconvert(src string) {
var newprio []int
for _, c := range []byte(src) {
if i := int(c); (i == 0) || (i == 128) { // if not selected
newprio = append(newprio, 0)
} else if (i == 4) || (i == 8) { // if low or normal prio
newprio = append(newprio, 1)
} else if i == 12 { // if high prio
newprio = append(newprio, 6)
} else {
newprio = append(newprio, 0)
}
}
newstructure.FilePriority = newprio
}
func (newstructure *NewTorrentStructure) fillmissing() {
newstructure.ifcompletedon()
newstructure.fillsizes()
newstructure.fillsavepaths()
if newstructure.Unfinished != nil {
newstructure.Pieces = newstructure.fillnothavefiles("0")
} else {
if newstructure.hasFiles {
newstructure.Pieces = newstructure.fillhavefiles()
} else {
newstructure.Pieces = newstructure.fillnothavefiles("1")
}
}
}
func (newstructure *NewTorrentStructure) fillsizes() {
newstructure.fileSizes = 0
if newstructure.hasFiles {
var filelists [][]int64
for num, file := range newstructure.torrentfile["info"].(map[string]interface{})["files"].([]interface{}) {
var lenght, mtime int64
var filestrings []string
if path, ok := file.(map[string]interface{})["path.utf-8"].([]interface{}); ok {
for _, f := range path {
filestrings = append(filestrings, f.(string))
}
} else {
for _, f := range file.(map[string]interface{})["path"].([]interface{}) {
filestrings = append(filestrings, f.(string))
}
}
filename := strings.Join(filestrings, string(os.PathSeparator))
newstructure.torrentFileList = append(newstructure.torrentFileList, filename)
fullpath := newstructure.path + string(os.PathSeparator) + filename
newstructure.fileSizes += file.(map[string]interface{})["length"].(int64)
if n := newstructure.FilePriority[num]; n != 0 {
lenght = file.(map[string]interface{})["length"].(int64)
newstructure.sizeAndPrio = append(newstructure.sizeAndPrio, []int64{lenght, 1})
mtime = fmtime(fullpath)
} else {
lenght, mtime = 0, 0
newstructure.sizeAndPrio = append(newstructure.sizeAndPrio,
[]int64{file.(map[string]interface{})["length"].(int64), 0})
}
flenmtime := []int64{lenght, mtime}
filelists = append(filelists, flenmtime)
}
newstructure.Filesizes = filelists
} else {
newstructure.fileSizes = newstructure.torrentfile["info"].(map[string]interface{})["length"].(int64)
newstructure.Filesizes = [][]int64{{newstructure.torrentfile["info"].(map[string]interface{})["length"].(int64),
fmtime(newstructure.path)}}
}
}
func (newstructure *NewTorrentStructure) fillnothavefiles(chr string) []byte {
var newpieces = make([]byte, 0, newstructure.numPieces)
nchr, _ := strconv.Atoi(chr)
for i := int64(0); i < newstructure.numPieces; i++ {
newpieces = append(newpieces, byte(nchr))
}
return newpieces
}
func (newstructure *NewTorrentStructure) gethash() (hash string) {
torinfo, _ := bencode.EncodeString(newstructure.torrentfile["info"].(map[string]interface{}))
h := sha1.New()
io.WriteString(h, torinfo)
hash = hex.EncodeToString(h.Sum(nil))
return
}
func (newstructure *NewTorrentStructure) fillhavefiles() []byte {
var newpieces = make([]byte, 0, newstructure.numPieces)
var allocation [][]int64
chrone, _ := strconv.Atoi("1")
chrzero, _ := strconv.Atoi("0")
offset := int64(0)
for _, pair := range newstructure.sizeAndPrio {
allocation = append(allocation, []int64{offset + 1, offset + pair[0], pair[1]})
offset = offset + pair[0]
}
for i := int64(0); i < newstructure.numPieces; i++ {
belongs := false
first, last := i*newstructure.pieceLenght, (i+1)*newstructure.pieceLenght
for _, trio := range allocation {
if (first >= trio[0]-newstructure.pieceLenght && last <= trio[1]+newstructure.pieceLenght) && trio[2] == 1 {
belongs = true
}
}
if belongs {
newpieces = append(newpieces, byte(chrone))
} else {
newpieces = append(newpieces, byte(chrzero))
}
}
return newpieces
}
func (newstructure *NewTorrentStructure) fillsavepaths() {
var torrentname string
if name, ok := newstructure.torrentfile["info"].(map[string]interface{})["name.utf-8"].(string); ok {
torrentname = name
} else {
torrentname = newstructure.torrentfile["info"].(map[string]interface{})["name"].(string)
}
origpath := newstructure.path
_, lastdirname := filepath.Split(strings.Replace(origpath, string(os.PathSeparator), "/", -1))
if newstructure.hasFiles {
if lastdirname == torrentname {
newstructure.QbthasRootFolder = 1
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
} else {
newstructure.QbthasRootFolder = 0
newstructure.SavePath = newstructure.path + string(os.PathSeparator)
newstructure.MappedFiles = newstructure.torrentFileList
}
} else {
if lastdirname == torrentname {
newstructure.QbthasRootFolder = 0
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
} else {
newstructure.QbthasRootFolder = 0
newstructure.torrentFileList = append(newstructure.torrentFileList, lastdirname)
newstructure.MappedFiles = newstructure.torrentFileList
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
}
}
if len(newstructure.replace) != 0 {
for _, pattern := range newstructure.replace {
newstructure.SavePath = strings.ReplaceAll(newstructure.SavePath, pattern.from, pattern.to)
}
}
newstructure.QbtsavePath = newstructure.SavePath
}
func logic(key string, value map[string]interface{}, flags *Flags, chans *Channels, position int, wg *sync.WaitGroup) error {
defer wg.Done()
defer func() {
@ -410,141 +127,162 @@ func logic(key string, value map[string]interface{}, flags *Flags, chans *Channe
}
}()
var err error
newstructure := NewTorrentStructure{ActiveTime: 0, AddedTime: 0, AnnounceToDht: 0, AnnounceToLsd: 0,
newstructure := libtorrent.NewTorrentStructure{ActiveTime: 0, AddedTime: 0, AnnounceToDht: 0, AnnounceToLsd: 0,
AnnounceToTrackers: 0, AutoManaged: 0, CompletedTime: 0, DownloadRateLimit: -1,
Fileformat: "libtorrent resume file", Fileversion: 1, FinishedTime: 0, LastSeenComplete: 0,
Libtorrentversion: "1.1.6.0", MaxConnections: 100, MaxUploads: 100, NumDownloaded: 0, NumIncomplete: 0,
QbtqueuePosition: 1, QbtratioLimit: -2000, QbtseedStatus: 1, QbtseedingTimeLimit: -2, QbttempPathDisabled: 0,
FileFormat: "libtorrent resume file", FileVersion: 1, FinishedTime: 0, LastSeenComplete: 0,
LibTorrentVersion: "1.1.6.0", MaxConnections: 100, MaxUploads: 100, NumDownloaded: 0, NumIncomplete: 0,
QbtQueuePosition: 1, QbtRatioLimit: -2000, QbtSeedStatus: 1, QbtSeedingTimeLimit: -2, QbttempPathDisabled: 0,
SeedMode: 0, SeedingTime: 0, SequentialDownload: 0, SuperSeeding: 0, TotalDownloaded: 0, TotalUploaded: 0,
UploadRateLimit: 0, Qbtname: "", withoutLabels: flags.withoutLabels, withoutTags: flags.withoutTags}
if ok := filepath.IsAbs(key); ok {
newstructure.torrentFilePath = key
} else {
newstructure.torrentFilePath = flags.bitDir + key
}
if _, err = os.Stat(newstructure.torrentFilePath); os.IsNotExist(err) {
chans.errChannel <- fmt.Sprintf("Can't find torrent file %v for %v", newstructure.torrentFilePath, key)
return err
}
newstructure.torrentfile, err = decodetorrentfile(newstructure.torrentFilePath)
if err != nil {
chans.errChannel <- fmt.Sprintf("Can't decode torrent file %v for %v", newstructure.torrentFilePath, key)
return err
}
UploadRateLimit: 0, QbtName: "", WithoutLabels: flags.WithoutLabels, WithoutTags: flags.WithoutTags,
Separator: flags.PathSeparator}
if flags.replace != "" {
for _, str := range strings.Split(flags.replace, ";") {
patterns := strings.Split(str, ",")
newstructure.replace = append(newstructure.replace, Replace{
from: patterns[0],
to: patterns[1],
})
if isAbs, _ := regexp.MatchString(`^[A-Z]:\\`, key); isAbs == true {
if runtime.GOOS == "windows" {
newstructure.TorrentFilePath = key
} else { // for unix system find in search paths
pathparts := strings.Split(key, "\\")
newstructure.TorrentFilePath = pathparts[len(pathparts)-1]
}
} else {
newstructure.TorrentFilePath = flags.BitDir + key // additional search required
}
if _, err = os.Stat(newstructure.TorrentFilePath); os.IsNotExist(err) {
for _, searchPath := range flags.SearchPaths {
if _, err = os.Stat(searchPath + newstructure.TorrentFilePath); err == nil {
newstructure.TorrentFilePath = searchPath + newstructure.TorrentFilePath
goto CONTINUE
}
}
chans.errChannel <- fmt.Sprintf("Can't find torrent file %v for %v", newstructure.TorrentFilePath, key)
return err
CONTINUE:
}
newstructure.TorrentFile, err = decodetorrentfile(newstructure.TorrentFilePath)
if err != nil {
chans.errChannel <- fmt.Sprintf("Can't decode torrent file %v for %v", newstructure.TorrentFilePath, key)
return err
}
if _, ok := newstructure.torrentfile["info"].(map[string]interface{})["files"]; ok {
newstructure.hasFiles = true
for _, str := range flags.Replaces {
patterns := strings.Split(str, ",")
newstructure.Replace = append(newstructure.Replace, replace.Replace{
From: patterns[0],
To: patterns[1],
})
}
if _, ok := newstructure.TorrentFile["info"].(map[string]interface{})["files"]; ok {
newstructure.HasFiles = true
} else {
newstructure.hasFiles = false
newstructure.HasFiles = false
}
if value["path"].(string)[len(value["path"].(string))-1] == os.PathSeparator {
newstructure.path = value["path"].(string)[:len(value["path"].(string))-1]
newstructure.Path = value["path"].(string)[:len(value["path"].(string))-1]
} else {
newstructure.path = value["path"].(string)
newstructure.Path = value["path"].(string)
}
newstructure.ActiveTime = value["runtime"].(int64)
newstructure.AddedTime = value["added_on"].(int64)
newstructure.CompletedTime = value["completed_on"].(int64)
newstructure.Infohash = value["info"].(string)
newstructure.InfoHash = value["info"].(string)
newstructure.SeedingTime = value["runtime"].(int64)
newstructure.QbtqueuePosition = position
newstructure.started(value["started"].(int64))
newstructure.QbtQueuePosition = position
newstructure.Started(value["started"].(int64))
newstructure.FinishedTime = int64(time.Since(time.Unix(value["completed_on"].(int64), 0)).Minutes())
newstructure.TotalDownloaded = value["downloaded"].(int64)
newstructure.TotalUploaded = value["uploaded"].(int64)
newstructure.UploadRateLimit = value["upspeed"].(int64)
newstructure.iftags(value["labels"])
newstructure.IfTags(value["labels"])
if value["label"] != nil {
newstructure.iflabel(value["label"].(string))
newstructure.IfLabel(value["label"].(string))
} else {
newstructure.iflabel("")
newstructure.IfLabel("")
}
newstructure.gettrackers(value["trackers"])
newstructure.prioconvert(value["prio"].(string))
newstructure.GetTrackers(value["trackers"])
newstructure.PrioConvert(value["prio"].(string))
// https://libtorrent.org/manual-ref.html#fast-resume
newstructure.Blockperpiece = newstructure.torrentfile["info"].(map[string]interface{})["piece length"].(int64) / 16 / 1024
newstructure.pieceLenght = newstructure.torrentfile["info"].(map[string]interface{})["piece length"].(int64)
newstructure.BlockPerPiece = newstructure.TorrentFile["info"].(map[string]interface{})["piece length"].(int64) / 16 / 1024
newstructure.PieceLenght = newstructure.TorrentFile["info"].(map[string]interface{})["piece length"].(int64)
/*
pieces maps to a string whose length is a multiple of 20. It is to be subdivided into strings of length 20,
each of which is the SHA1 hash of the piece at the corresponding index.
http://www.bittorrent.org/beps/bep_0003.html
*/
newstructure.numPieces = int64(len(newstructure.torrentfile["info"].(map[string]interface{})["pieces"].(string))) / 20
newstructure.fillmissing()
newbasename := newstructure.gethash()
newstructure.NumPieces = int64(len(newstructure.TorrentFile["info"].(map[string]interface{})["pieces"].(string))) / 20
newstructure.FillMissing()
newbasename := newstructure.GetHash()
if err = encodetorrentfile(flags.qBitDir+newbasename+".fastresume", &newstructure); err != nil {
chans.errChannel <- fmt.Sprintf("Can't create qBittorrent fastresume file %v", flags.qBitDir+newbasename+".fastresume")
if err = encodetorrentfile(flags.QBitDir+newbasename+".fastresume", &newstructure); err != nil {
chans.errChannel <- fmt.Sprintf("Can't create qBittorrent fastresume file %v", flags.QBitDir+newbasename+".fastresume")
return err
}
if err = copyfile(newstructure.torrentFilePath, flags.qBitDir+newbasename+".torrent"); err != nil {
chans.errChannel <- fmt.Sprintf("Can't create qBittorrent torrent file %v", flags.qBitDir+newbasename+".torrent")
if err = copyfile(newstructure.TorrentFilePath, flags.QBitDir+newbasename+".torrent"); err != nil {
chans.errChannel <- fmt.Sprintf("Can't create qBittorrent torrent file %v", flags.QBitDir+newbasename+".torrent")
return err
}
chans.comChannel <- fmt.Sprintf("Sucessfully imported %v", key)
return nil
}
func openBrowser(url string) {
var err error
switch runtime.GOOS {
case "linux":
err = exec.Command("xdg-open", url).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
case "darwin":
err = exec.Command("open", url).Start()
default:
err = fmt.Errorf("unsupported platform")
}
if err != nil {
log.Fatal(err)
}
}
func main() {
flags := Flags{}
flags := Flags{PathSeparator: string(os.PathSeparator)}
sep := string(os.PathSeparator)
switch OS := runtime.GOOS; OS {
case "windows":
flags.bitDir = os.Getenv("APPDATA") + sep + "uTorrent" + sep
flags.config = os.Getenv("APPDATA") + sep + "qBittorrent" + sep + "qBittorrent.ini"
flags.qBitDir = os.Getenv("LOCALAPPDATA") + sep + "qBittorrent" + sep + "BT_backup" + sep
flags.BitDir = os.Getenv("APPDATA") + sep + "uTorrent" + sep
flags.Config = os.Getenv("APPDATA") + sep + "qBittorrent" + sep + "qBittorrent.ini"
flags.QBitDir = os.Getenv("LOCALAPPDATA") + sep + "qBittorrent" + sep + "BT_backup" + sep
case "linux":
usr, err := user.Current()
if err != nil {
panic(err)
}
flags.bitDir = "/mnt/uTorrent/"
flags.config = usr.HomeDir + sep + ".config" + sep + "qBittorrent" + sep + "qBittorrent.conf"
flags.qBitDir = usr.HomeDir + sep + ".local" + sep + "share" + sep + "data" + sep + "qBittorrent" + sep + "BT_backup" + sep
flags.BitDir = "/mnt/uTorrent/"
flags.Config = usr.HomeDir + sep + ".config" + sep + "qBittorrent" + sep + "qBittorrent.conf"
flags.QBitDir = usr.HomeDir + sep + ".local" + sep + "share" + sep + "data" + sep + "qBittorrent" + sep + "BT_backup" + sep
case "darwin":
usr, err := user.Current()
if err != nil {
panic(err)
}
flags.bitDir = usr.HomeDir + sep + "Library" + sep + "Application Support" + sep + "uTorrent" + sep
flags.config = usr.HomeDir + sep + ".config" + sep + "qBittorrent" + sep + "qbittorrent.ini"
flags.qBitDir = usr.HomeDir + sep + "Library" + sep + "Application Support" + sep + "QBittorrent" + sep + "BT_backup" + sep
flags.BitDir = usr.HomeDir + sep + "Library" + sep + "Application Support" + sep + "uTorrent" + sep
flags.Config = usr.HomeDir + sep + ".config" + sep + "qBittorrent" + sep + "qbittorrent.ini"
flags.QBitDir = usr.HomeDir + sep + "Library" + sep + "Application Support" + sep + "QBittorrent" + sep + "BT_backup" + sep
}
gnuflag.StringVar(&flags.bitDir, "source", flags.bitDir,
"Source directory that contains resume.dat and torrents files")
gnuflag.StringVar(&flags.bitDir, "s", flags.bitDir,
"Source directory that contains resume.dat and torrents files")
gnuflag.StringVar(&flags.qBitDir, "destination", flags.qBitDir,
"Destination directory BT_backup (as default)")
gnuflag.StringVar(&flags.qBitDir, "d", flags.qBitDir,
"Destination directory BT_backup (as default)")
gnuflag.StringVar(&flags.config, "qconfig", flags.config,
"qBittorrent config files (for write tags)")
gnuflag.StringVar(&flags.config, "c", flags.config,
"qBittorrent config files (for write tags)")
gnuflag.BoolVar(&flags.withoutLabels, "without-labels", false, "Do not export/import labels")
gnuflag.BoolVar(&flags.withoutTags, "without-tags", false, "Do not export/import tags")
gnuflag.StringVar(&flags.replace, "replace", "", "Replace paths.\n "+
"Delimiter for replaces - ;\n "+
"Delimiter for from/to - ,\n Example: \"D:\\films,/home/user/films;\\,/\"\n "+
"If you use path separator different from you system, declare it mannually")
gnuflag.Parse(true)
_, err := goflags.Parse(&flags)
if _, err := goflags.Parse(&flags); err != nil { // https://godoc.org/github.com/jessevdk/go-flags#ErrorType
if flagsErr, ok := err.(*goflags.Error); ok && flagsErr.Type == goflags.ErrHelp {
os.Exit(0)
} else {
log.Println(err)
time.Sleep(30 * time.Second)
os.Exit(1)
}
}
if flags.replace != "" {
for _, str := range strings.Split(flags.replace, ";") {
if len(flags.Replaces) != 0 {
for _, str := range flags.Replaces {
patterns := strings.Split(str, ",")
if len(patterns) < 2 {
log.Println("Bad replace pattern")
@ -554,24 +292,29 @@ func main() {
}
}
if flags.bitDir[len(flags.bitDir)-1] != os.PathSeparator {
flags.bitDir += string(os.PathSeparator)
if flags.BitDir[len(flags.BitDir)-1] != os.PathSeparator {
flags.BitDir += string(os.PathSeparator)
}
if flags.qBitDir[len(flags.qBitDir)-1] != os.PathSeparator {
flags.qBitDir += string(os.PathSeparator)
if flags.QBitDir[len(flags.QBitDir)-1] != os.PathSeparator {
flags.QBitDir += string(os.PathSeparator)
}
for index, searchPath := range flags.SearchPaths {
if searchPath[len(searchPath)-1] != os.PathSeparator {
flags.SearchPaths[index] += string(os.PathSeparator)
}
}
if _, err := os.Stat(flags.bitDir); os.IsNotExist(err) {
if _, err := os.Stat(flags.BitDir); os.IsNotExist(err) {
log.Println("Can't find uTorrent\\Bittorrent folder")
time.Sleep(30 * time.Second)
os.Exit(1)
}
if _, err := os.Stat(flags.qBitDir); os.IsNotExist(err) {
if _, err := os.Stat(flags.QBitDir); os.IsNotExist(err) {
log.Println("Can't find qBittorrent folder")
time.Sleep(30 * time.Second)
os.Exit(1)
}
resumefilepath := flags.bitDir + "resume.dat"
resumefilepath := flags.BitDir + "resume.dat"
if _, err := os.Stat(resumefilepath); os.IsNotExist(err) {
log.Println("Can't find uTorrent\\Bittorrent resume file")
time.Sleep(30 * time.Second)
@ -583,33 +326,41 @@ func main() {
time.Sleep(30 * time.Second)
os.Exit(1)
}
if flags.withoutTags == false {
if _, err := os.Stat(flags.config); os.IsNotExist(err) {
if flags.WithoutTags == false {
if _, err := os.Stat(flags.Config); os.IsNotExist(err) {
fmt.Println("Can not read qBittorrent config file. Try run and close qBittorrent if you have not done" +
" so already, or specify the path explicitly or do not import tags")
time.Sleep(30 * time.Second)
os.Exit(1)
}
}
color.Green("It will be performed processing from directory %v to directory %v\n", flags.bitDir, flags.qBitDir)
totaljobs := len(resumefile)
chans := Channels{comChannel: make(chan string, totaljobs),
errChannel: make(chan string, totaljobs),
boundedChannel: make(chan bool, runtime.GOMAXPROCS(0)*2)}
color.Green("It will be performed processing from directory %v to directory %v\n", flags.BitDir, flags.QBitDir)
color.HiRed("Check that the qBittorrent is turned off and the directory %v and config %v is backed up.\n\n",
flags.qBitDir, flags.config)
flags.QBitDir, flags.Config)
fmt.Println("Press Enter to start")
fmt.Scanln()
log.Println("Started")
totaljobs := len(resumefile)
transfertorrents(chans, flags, resumefile, totaljobs)
fmt.Println("\nPress Enter to exit")
fmt.Scanln()
}
func transfertorrents(chans Channels, flags Flags, resumefile map[string]interface{}, totaljobs int) {
numjob := 1
var oldtags string
var newtags []string
var wg sync.WaitGroup
chans := Channels{comChannel: make(chan string, totaljobs),
errChannel: make(chan string, totaljobs),
boundedChannel: make(chan bool, runtime.GOMAXPROCS(0)*2)}
positionnum := 0
for key, value := range resumefile {
if key != ".fileguard" && key != "rec" {
positionnum++
if flags.withoutTags == false {
if flags.WithoutTags == false {
if labels, ok := value.(map[string]interface{})["labels"]; ok {
for _, label := range labels.([]interface{}) {
if len(label.(string)) > 0 {
@ -642,8 +393,8 @@ func main() {
waserrors = true
numjob++
}
if flags.withoutTags == false {
cfg, err := ini.Load(flags.config)
if flags.WithoutTags == false {
cfg, err := ini.Load(flags.Config)
ini.PrettyFormat = false
ini.PrettySection = false
if err != nil {
@ -674,13 +425,11 @@ func main() {
} else {
cfg.Section("BitTorrent").NewKey("Session\\Tags", strings.Join(newtags, ", "))
}
cfg.SaveTo(flags.config)
cfg.SaveTo(flags.Config)
}
fmt.Println()
log.Println("Ended")
if waserrors {
log.Println("Not all torrents was processed")
}
fmt.Println("\nPress Enter to exit")
fmt.Scanln()
}

View File

@ -1,7 +1,7 @@
#!/bin/bash
GOOS=windows GOACH=amd64 go build -o bt2qbt_v${1}_amd64.exe
GOOS=windows GOARCH=386 go build -o bt2qbt_v${1}_i386.exe
GOOS=linux GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_linux
GOOS=linux GOARCH=386 go build -o bt2qbt_v${1}_i386_linux
GOOS=darwin GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_macos
GOOS=darwin GOARCH=386 go build -o bt2qbt_v${1}_i386_macos
GOOS=windows GOACH=amd64 go build -o bt2qbt_v${1}_amd64.exe -tags forceposix
GOOS=windows GOARCH=386 go build -o bt2qbt_v${1}_i386.exe -tags forceposix
GOOS=linux GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_linux -tags forceposix
GOOS=linux GOARCH=386 go build -o bt2qbt_v${1}_i386_linux -tags forceposix
GOOS=darwin GOARCH=amd64 go build -o bt2qbt_v${1}_amd64_macos -tags forceposix
GOOS=darwin GOARCH=386 go build -o bt2qbt_v${1}_i386_macos -tags forceposix

2
go.mod
View File

@ -5,8 +5,8 @@ go 1.13
require (
github.com/fatih/color v1.9.0
github.com/go-ini/ini v1.52.0
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d
github.com/mattn/go-isatty v0.0.12 // indirect
github.com/zeebo/bencode v1.0.0
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
github.com/jessevdk/go-flags v1.4.0
)

2
go.sum
View File

@ -2,6 +2,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/go-ini/ini v1.52.0 h1:3UeUAveYUTCYV/G0jNDiIrrtIeAl1oAjshYyU2PaAlQ=
github.com/go-ini/ini v1.52.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d h1:c93kUJDtVAXFEhsCh5jSxyOJmFHuzcihnslQiX8Urwo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=

306
libtorrent/torrent.go Normal file
View File

@ -0,0 +1,306 @@
package libtorrent
import (
"crypto/sha1"
"encoding/hex"
"github.com/rumanzo/bt2qbt/replace"
"github.com/zeebo/bencode"
"io"
"os"
"strconv"
"strings"
"time"
)
type NewTorrentStructure struct {
ActiveTime int64 `bencode:"active_time"`
AddedTime int64 `bencode:"added_time"`
AnnounceToDht int64 `bencode:"announce_to_dht"`
AnnounceToLsd int64 `bencode:"announce_to_lsd"`
AnnounceToTrackers int64 `bencode:"announce_to_trackers"`
AutoManaged int64 `bencode:"auto_managed"`
BannedPeers string `bencode:"banned_peers"`
BannedPeers6 string `bencode:"banned_peers6"`
BlockPerPiece int64 `bencode:"blocks per piece"`
CompletedTime int64 `bencode:"completed_time"`
DownloadRateLimit int64 `bencode:"download_rate_limit"`
FileSizes [][]int64 `bencode:"file sizes"`
FileFormat string `bencode:"file-format"`
FileVersion int64 `bencode:"file-version"`
FilePriority []int `bencode:"file_priority"`
FinishedTime int64 `bencode:"finished_time"`
InfoHash string `bencode:"info-hash"`
LastSeenComplete int64 `bencode:"last_seen_complete"`
LibTorrentVersion string `bencode:"libtorrent-version"`
MaxConnections int64 `bencode:"max_connections"`
MaxUploads int64 `bencode:"max_uploads"`
NumDownloaded int64 `bencode:"num_downloaded"`
NumIncomplete int64 `bencode:"num_incomplete"`
MappedFiles []string `bencode:"mapped_files,omitempty"`
Paused int64 `bencode:"paused"`
Peers string `bencode:"peers"`
Peers6 string `bencode:"peers6"`
Pieces []byte `bencode:"pieces"`
QbthasRootFolder int64 `bencode:"qBt-hasRootFolder"`
QbtCategory string `bencode:"qBt-category,omitempty"`
QbtName string `bencode:"qBt-name"`
QbtQueuePosition int `bencode:"qBt-queuePosition"`
QbtRatioLimit int64 `bencode:"qBt-ratioLimit"`
QbtSavePath string `bencode:"qBt-savePath"`
QbtSeedStatus int64 `bencode:"qBt-seedStatus"`
QbtSeedingTimeLimit int64 `bencode:"qBt-seedingTimeLimit"`
QbtTags []string `bencode:"qBt-tags"`
QbttempPathDisabled int64 `bencode:"qBt-tempPathDisabled"`
SavePath string `bencode:"save_path"`
SeedMode int64 `bencode:"seed_mode"`
SeedingTime int64 `bencode:"seeding_time"`
SequentialDownload int64 `bencode:"sequential_download"`
SuperSeeding int64 `bencode:"super_seeding"`
TotalDownloaded int64 `bencode:"total_downloaded"`
TotalUploaded int64 `bencode:"total_uploaded"`
Trackers [][]string `bencode:"trackers"`
UploadRateLimit int64 `bencode:"upload_rate_limit"`
Unfinished *[]interface{} `bencode:"unfinished,omitempty"`
WithoutLabels bool `bencode:"-"`
WithoutTags bool `bencode:"-"`
HasFiles bool `bencode:"-"`
TorrentFilePath string `bencode:"-"`
TorrentFile map[string]interface{} `bencode:"-"`
Path string `bencode:"-"`
fileSizes int64 `bencode:"-"`
sizeAndPrio [][]int64 `bencode:"-"`
torrentFileList []string `bencode:"-"`
NumPieces int64 `bencode:"-"`
PieceLenght int64 `bencode:"-"`
Replace []replace.Replace `bencode:"-"`
Separator string `bencode:"-"`
}
func (newstructure *NewTorrentStructure) Started(started int64) {
if started == 0 {
newstructure.Paused = 1
newstructure.AutoManaged = 0
newstructure.AnnounceToDht = 0
newstructure.AnnounceToLsd = 0
newstructure.AnnounceToTrackers = 0
} else {
newstructure.Paused = 0
newstructure.AutoManaged = 1
newstructure.AnnounceToDht = 1
newstructure.AnnounceToLsd = 1
newstructure.AnnounceToTrackers = 1
}
}
func (newstructure *NewTorrentStructure) IfCompletedOn() {
if newstructure.CompletedTime != 0 {
newstructure.LastSeenComplete = time.Now().Unix()
} else {
newstructure.Unfinished = new([]interface{})
}
}
func (newstructure *NewTorrentStructure) IfTags(labels interface{}) {
if newstructure.WithoutTags == false && labels != nil {
for _, label := range labels.([]interface{}) {
if label != nil {
newstructure.QbtTags = append(newstructure.QbtTags, label.(string))
}
}
} else {
newstructure.QbtTags = []string{}
}
}
func (newstructure *NewTorrentStructure) IfLabel(label interface{}) {
if newstructure.WithoutLabels == false {
switch label.(type) {
case nil:
newstructure.QbtCategory = ""
case string:
newstructure.QbtCategory = label.(string)
}
} else {
newstructure.QbtCategory = ""
}
}
func (newstructure *NewTorrentStructure) GetTrackers(trackers interface{}) {
switch strct := trackers.(type) {
case []interface{}:
for _, st := range strct {
newstructure.GetTrackers(st)
}
case string:
for _, str := range strings.Fields(strct) {
newstructure.Trackers = append(newstructure.Trackers, []string{str})
}
}
}
func (newstructure *NewTorrentStructure) PrioConvert(src string) {
var newprio []int
for _, c := range []byte(src) {
if i := int(c); (i == 0) || (i == 128) { // if not selected
newprio = append(newprio, 0)
} else if (i == 4) || (i == 8) { // if low or normal prio
newprio = append(newprio, 1)
} else if i == 12 { // if high prio
newprio = append(newprio, 6)
} else {
newprio = append(newprio, 0)
}
}
newstructure.FilePriority = newprio
}
func (newstructure *NewTorrentStructure) FillMissing() {
newstructure.IfCompletedOn()
newstructure.FillSizes()
newstructure.FillSavePaths()
if newstructure.Unfinished != nil {
newstructure.Pieces = newstructure.FillNotHaveFiles("0")
} else {
if newstructure.HasFiles {
newstructure.Pieces = newstructure.FillHaveFiles()
} else {
newstructure.Pieces = newstructure.FillNotHaveFiles("1")
}
}
}
func (newstructure *NewTorrentStructure) FillSizes() {
newstructure.fileSizes = 0
if newstructure.HasFiles {
var filelists [][]int64
for num, file := range newstructure.TorrentFile["info"].(map[string]interface{})["files"].([]interface{}) {
var lenght, mtime int64
var filestrings []string
if path, ok := file.(map[string]interface{})["path.utf-8"].([]interface{}); ok {
for _, f := range path {
filestrings = append(filestrings, f.(string))
}
} else {
for _, f := range file.(map[string]interface{})["path"].([]interface{}) {
filestrings = append(filestrings, f.(string))
}
}
filename := strings.Join(filestrings, newstructure.Separator)
newstructure.torrentFileList = append(newstructure.torrentFileList, filename)
fullpath := newstructure.Path + newstructure.Separator + filename
newstructure.fileSizes += file.(map[string]interface{})["length"].(int64)
if n := newstructure.FilePriority[num]; n != 0 {
lenght = file.(map[string]interface{})["length"].(int64)
newstructure.sizeAndPrio = append(newstructure.sizeAndPrio, []int64{lenght, 1})
mtime = fmtime(fullpath)
} else {
lenght, mtime = 0, 0
newstructure.sizeAndPrio = append(newstructure.sizeAndPrio,
[]int64{file.(map[string]interface{})["length"].(int64), 0})
}
flenmtime := []int64{lenght, mtime}
filelists = append(filelists, flenmtime)
}
newstructure.FileSizes = filelists
} else {
newstructure.fileSizes = newstructure.TorrentFile["info"].(map[string]interface{})["length"].(int64)
newstructure.FileSizes = [][]int64{{newstructure.TorrentFile["info"].(map[string]interface{})["length"].(int64),
fmtime(newstructure.Path)}}
}
}
func (newstructure *NewTorrentStructure) FillNotHaveFiles(chr string) []byte {
var newpieces = make([]byte, 0, newstructure.NumPieces)
nchr, _ := strconv.Atoi(chr)
for i := int64(0); i < newstructure.NumPieces; i++ {
newpieces = append(newpieces, byte(nchr))
}
return newpieces
}
func (newstructure *NewTorrentStructure) GetHash() (hash string) {
torinfo, _ := bencode.EncodeString(newstructure.TorrentFile["info"].(map[string]interface{}))
h := sha1.New()
io.WriteString(h, torinfo)
hash = hex.EncodeToString(h.Sum(nil))
return
}
func (newstructure *NewTorrentStructure) FillHaveFiles() []byte {
var newpieces = make([]byte, 0, newstructure.NumPieces)
var allocation [][]int64
chrone, _ := strconv.Atoi("1")
chrzero, _ := strconv.Atoi("0")
offset := int64(0)
for _, pair := range newstructure.sizeAndPrio {
allocation = append(allocation, []int64{offset + 1, offset + pair[0], pair[1]})
offset = offset + pair[0]
}
for i := int64(0); i < newstructure.NumPieces; i++ {
belongs := false
first, last := i*newstructure.PieceLenght, (i+1)*newstructure.PieceLenght
for _, trio := range allocation {
if (first >= trio[0]-newstructure.PieceLenght && last <= trio[1]+newstructure.PieceLenght) && trio[2] == 1 {
belongs = true
}
}
if belongs {
newpieces = append(newpieces, byte(chrone))
} else {
newpieces = append(newpieces, byte(chrzero))
}
}
return newpieces
}
func (newstructure *NewTorrentStructure) FillSavePaths() {
var torrentname string
if name, ok := newstructure.TorrentFile["info"].(map[string]interface{})["name.utf-8"].(string); ok {
torrentname = name
} else {
torrentname = newstructure.TorrentFile["info"].(map[string]interface{})["name"].(string)
}
origpath := newstructure.Path
dirpaths := strings.Split(origpath, "\\")
lastdirname := dirpaths[len(dirpaths)-1]
if newstructure.HasFiles {
if lastdirname == torrentname {
newstructure.QbthasRootFolder = 1
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
} else {
newstructure.QbthasRootFolder = 0
newstructure.SavePath = newstructure.Path + newstructure.Separator
newstructure.MappedFiles = newstructure.torrentFileList
}
} else {
if lastdirname == torrentname {
newstructure.QbthasRootFolder = 0
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
} else {
newstructure.QbthasRootFolder = 0
newstructure.torrentFileList = append(newstructure.torrentFileList, lastdirname)
newstructure.MappedFiles = newstructure.torrentFileList
newstructure.SavePath = origpath[0 : len(origpath)-len(lastdirname)]
}
}
for _, pattern := range newstructure.Replace {
newstructure.SavePath = strings.ReplaceAll(newstructure.SavePath, pattern.From, pattern.To)
}
switch newstructure.Separator {
case "\\":
newstructure.SavePath = strings.ReplaceAll(newstructure.SavePath, "/", newstructure.Separator)
case "/":
newstructure.SavePath = strings.ReplaceAll(newstructure.SavePath, "\\", newstructure.Separator)
}
newstructure.QbtSavePath = newstructure.SavePath
}
func fmtime(path string) (mtime int64) {
if fi, err := os.Stat(path); err != nil {
return 0
} else {
mtime = fi.ModTime().Unix()
return
}
}

5
replace/replace.go Normal file
View File

@ -0,0 +1,5 @@
package replace
type Replace struct {
From, To string
}

View File

@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,10 +0,0 @@
Gnuflag
-----
The gnuflag package is a fork of the Go standard library
package that supports GNU-compatible flag syntax.
In particular, it supports `--longflag` and `-l` single-character
flag syntax.
Full documentation can be found here: https://godoc.org/github.com/juju/gnuflag.

View File

@ -1,954 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package flag implements command-line flag parsing in the GNU style.
It is almost exactly the same as the standard flag package,
the only difference being the extra argument to Parse.
Command line flag syntax:
-f // single letter flag
-fg // two single letter flags together
--flag // multiple letter flag
--flag x // non-boolean flags only
-f x // non-boolean flags only
-fx // if f is a non-boolean flag, x is its argument.
The last three forms are not permitted for boolean flags because the
meaning of the command
cmd -f *
will change if there is a file called 0, false, etc. There is currently
no way to turn off a boolean flag.
Flag parsing stops after the terminator "--", or just before the first
non-flag argument ("-" is a non-flag argument) if the interspersed
argument to Parse is false.
*/
package gnuflag
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
)
// ErrHelp is the error returned if the -help or -h flag is invoked
// but no such flag is defined.
var ErrHelp = errors.New("help requested")
// -- bool Value
type boolValue bool
func newBoolValue(val bool, p *bool) *boolValue {
*p = val
return (*boolValue)(p)
}
func (b *boolValue) Set(s string) error {
v, err := strconv.ParseBool(s)
*b = boolValue(v)
return err
}
func (b *boolValue) Get() interface{} { return bool(*b) }
func (b *boolValue) String() string { return fmt.Sprintf("%v", *b) }
func (b *boolValue) IsBoolFlag() bool { return true }
// optional interface to indicate boolean flags that can be
// supplied without "=value" text
type boolFlag interface {
Value
IsBoolFlag() bool
}
// -- int Value
type intValue int
func newIntValue(val int, p *int) *intValue {
*p = val
return (*intValue)(p)
}
func (i *intValue) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 64)
*i = intValue(v)
return err
}
func (i *intValue) Get() interface{} { return int(*i) }
func (i *intValue) String() string { return fmt.Sprintf("%v", *i) }
// -- int64 Value
type int64Value int64
func newInt64Value(val int64, p *int64) *int64Value {
*p = val
return (*int64Value)(p)
}
func (i *int64Value) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 64)
*i = int64Value(v)
return err
}
func (i *int64Value) Get() interface{} { return int64(*i) }
func (i *int64Value) String() string { return fmt.Sprintf("%v", *i) }
// -- uint Value
type uintValue uint
func newUintValue(val uint, p *uint) *uintValue {
*p = val
return (*uintValue)(p)
}
func (i *uintValue) Set(s string) error {
v, err := strconv.ParseUint(s, 0, 64)
*i = uintValue(v)
return err
}
func (i *uintValue) Get() interface{} { return uint(*i) }
func (i *uintValue) String() string { return fmt.Sprintf("%v", *i) }
// -- uint64 Value
type uint64Value uint64
func newUint64Value(val uint64, p *uint64) *uint64Value {
*p = val
return (*uint64Value)(p)
}
func (i *uint64Value) Set(s string) error {
v, err := strconv.ParseUint(s, 0, 64)
*i = uint64Value(v)
return err
}
func (i *uint64Value) Get() interface{} { return uint64(*i) }
func (i *uint64Value) String() string { return fmt.Sprintf("%v", *i) }
// -- string Value
type stringValue string
func newStringValue(val string, p *string) *stringValue {
*p = val
return (*stringValue)(p)
}
func (s *stringValue) Set(val string) error {
*s = stringValue(val)
return nil
}
func (s *stringValue) Get() interface{} { return string(*s) }
func (s *stringValue) String() string { return fmt.Sprintf("%s", *s) }
// -- float64 Value
type float64Value float64
func newFloat64Value(val float64, p *float64) *float64Value {
*p = val
return (*float64Value)(p)
}
func (f *float64Value) Set(s string) error {
v, err := strconv.ParseFloat(s, 64)
*f = float64Value(v)
return err
}
func (f *float64Value) Get() interface{} { return float64(*f) }
func (f *float64Value) String() string { return fmt.Sprintf("%v", *f) }
// -- time.Duration Value
type durationValue time.Duration
func newDurationValue(val time.Duration, p *time.Duration) *durationValue {
*p = val
return (*durationValue)(p)
}
func (d *durationValue) Set(s string) error {
v, err := time.ParseDuration(s)
*d = durationValue(v)
return err
}
func (d *durationValue) Get() interface{} { return time.Duration(*d) }
func (d *durationValue) String() string { return (*time.Duration)(d).String() }
// Value is the interface to the dynamic value stored in a flag.
// (The default value is represented as a string.)
type Value interface {
String() string
Set(string) error
}
// Getter is an interface that allows the contents of a Value to be retrieved.
// It wraps the Value interface, rather than being part of it, because it
// appeared after Go 1 and its compatibility rules. All Value types provided
// by this package satisfy the Getter interface.
type Getter interface {
Value
Get() interface{}
}
// ErrorHandling defines how to handle flag parsing errors.
type ErrorHandling int
const (
ContinueOnError ErrorHandling = iota
ExitOnError
PanicOnError
)
// A FlagSet represents a set of defined flags.
type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
Usage func()
name string
parsed bool
actual map[string]*Flag
formal map[string]*Flag
args []string // arguments after flags
procArgs []string // arguments being processed (gnu only)
procFlag string // flag being processed (gnu only)
allowIntersperse bool // (gnu only)
exitOnError bool // does the program exit if there's an error?
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
// FlagKnownAs allows different projects to customise what their flags are
// known as, e.g. 'flag', 'option', 'item'. All error/log messages
// will use that name when referring to an individual items/flags in this set.
// For example, if this value is 'option', the default message 'value for flag'
// will become 'value for option'.
// Default value is 'flag'.
FlagKnownAs string
}
// A Flag represents the state of a flag.
type Flag struct {
Name string // name as it appears on command line
Usage string // help message
Value Value // value as set
DefValue string // default value (as text); for usage message
}
// sortFlags returns the flags as a slice in lexicographical sorted order.
func sortFlags(flags map[string]*Flag) []*Flag {
list := make(sort.StringSlice, len(flags))
i := 0
for _, f := range flags {
list[i] = f.Name
i++
}
list.Sort()
result := make([]*Flag, len(list))
for i, name := range list {
result[i] = flags[name]
}
return result
}
func (f *FlagSet) out() io.Writer {
if f.output == nil {
return os.Stderr
}
return f.output
}
// SetOutput sets the destination for usage and error messages.
// If output is nil, os.Stderr is used.
func (f *FlagSet) SetOutput(output io.Writer) {
f.output = output
}
// VisitAll visits the flags in lexicographical order, calling fn for each.
// It visits all flags, even those not set.
func (f *FlagSet) VisitAll(fn func(*Flag)) {
for _, flag := range sortFlags(f.formal) {
fn(flag)
}
}
// VisitAll visits the command-line flags in lexicographical order, calling
// fn for each. It visits all flags, even those not set.
func VisitAll(fn func(*Flag)) {
CommandLine.VisitAll(fn)
}
// Visit visits the flags in lexicographical order, calling fn for each.
// It visits only those flags that have been set.
func (f *FlagSet) Visit(fn func(*Flag)) {
for _, flag := range sortFlags(f.actual) {
fn(flag)
}
}
// Visit visits the command-line flags in lexicographical order, calling fn
// for each. It visits only those flags that have been set.
func Visit(fn func(*Flag)) {
CommandLine.Visit(fn)
}
// Lookup returns the Flag structure of the named flag, returning nil if none exists.
func (f *FlagSet) Lookup(name string) *Flag {
return f.formal[name]
}
// Lookup returns the Flag structure of the named command-line flag,
// returning nil if none exists.
func Lookup(name string) *Flag {
return CommandLine.formal[name]
}
// Set sets the value of the named flag.
func (f *FlagSet) Set(name, value string) error {
flag, ok := f.formal[name]
if !ok {
return fmt.Errorf("no such %v -%v", f.FlagKnownAs, name)
}
err := flag.Value.Set(value)
if err != nil {
return err
}
if f.actual == nil {
f.actual = make(map[string]*Flag)
}
f.actual[name] = flag
return nil
}
// Set sets the value of the named command-line flag.
func Set(name, value string) error {
return CommandLine.Set(name, value)
}
// flagsByLength is a slice of flags implementing sort.Interface,
// sorting primarily by the length of the flag, and secondarily
// alphabetically.
type flagsByLength []*Flag
func (f flagsByLength) Less(i, j int) bool {
s1, s2 := f[i].Name, f[j].Name
if len(s1) != len(s2) {
return len(s1) < len(s2)
}
return s1 < s2
}
func (f flagsByLength) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f flagsByLength) Len() int {
return len(f)
}
// flagsByName is a slice of slices of flags implementing sort.Interface,
// alphabetically sorting by the name of the first flag in each slice.
type flagsByName [][]*Flag
func (f flagsByName) Less(i, j int) bool {
return f[i][0].Name < f[j][0].Name
}
func (f flagsByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f flagsByName) Len() int {
return len(f)
}
// PrintDefaults prints, to standard error unless configured
// otherwise, the default values of all defined flags in the set.
// If there is more than one name for a given flag, the usage information and
// default value from the shortest will be printed (or the least alphabetically
// if there are several equally short flag names).
func (f *FlagSet) PrintDefaults() {
// group together all flags for a given value
flags := make(map[interface{}]flagsByLength)
f.VisitAll(func(f *Flag) {
flags[f.Value] = append(flags[f.Value], f)
})
// sort the output flags by shortest name for each group.
var byName flagsByName
for _, f := range flags {
sort.Sort(f)
byName = append(byName, f)
}
sort.Sort(byName)
var line bytes.Buffer
for _, fs := range byName {
line.Reset()
for i, f := range fs {
if i > 0 {
line.WriteString(", ")
}
line.WriteString(flagWithMinus(f.Name))
}
format := "%s (= %s)\n %s\n"
if _, ok := fs[0].Value.(*stringValue); ok {
// put quotes on the value
format = "%s (= %q)\n %s\n"
}
fmt.Fprintf(f.out(), format, line.Bytes(), fs[0].DefValue, fs[0].Usage)
}
}
// PrintDefaults prints to standard error the default values of all defined command-line flags.
func PrintDefaults() {
CommandLine.PrintDefaults()
}
// defaultUsage is the default function to print a usage message.
func defaultUsage(f *FlagSet) {
if f.name == "" {
fmt.Fprintf(f.out(), "Usage:\n")
} else {
fmt.Fprintf(f.out(), "Usage of %s:\n", f.name)
}
f.PrintDefaults()
}
// NOTE: Usage is not just defaultUsage(CommandLine)
// because it serves (via godoc flag Usage) as the example
// for how to write your own usage function.
// Usage prints to standard error a usage message documenting all defined command-line flags.
// The function is a variable that may be changed to point to a custom function.
var Usage = func() {
fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
PrintDefaults()
}
// NFlag returns the number of flags that have been set.
func (f *FlagSet) NFlag() int { return len(f.actual) }
// NFlag returns the number of command-line flags that have been set.
func NFlag() int { return len(CommandLine.actual) }
// Arg returns the i'th argument. Arg(0) is the first remaining argument
// after flags have been processed.
func (f *FlagSet) Arg(i int) string {
if i < 0 || i >= len(f.args) {
return ""
}
return f.args[i]
}
// Arg returns the i'th command-line argument. Arg(0) is the first remaining argument
// after flags have been processed.
func Arg(i int) string {
return CommandLine.Arg(i)
}
// NArg is the number of arguments remaining after flags have been processed.
func (f *FlagSet) NArg() int { return len(f.args) }
// NArg is the number of arguments remaining after flags have been processed.
func NArg() int { return len(CommandLine.args) }
// Args returns the non-flag arguments.
func (f *FlagSet) Args() []string { return f.args }
// Args returns the non-flag command-line arguments.
func Args() []string { return CommandLine.args }
// BoolVar defines a bool flag with specified name, default value, and usage string.
// The argument p points to a bool variable in which to store the value of the flag.
func (f *FlagSet) BoolVar(p *bool, name string, value bool, usage string) {
f.Var(newBoolValue(value, p), name, usage)
}
// BoolVar defines a bool flag with specified name, default value, and usage string.
// The argument p points to a bool variable in which to store the value of the flag.
func BoolVar(p *bool, name string, value bool, usage string) {
CommandLine.Var(newBoolValue(value, p), name, usage)
}
// Bool defines a bool flag with specified name, default value, and usage string.
// The return value is the address of a bool variable that stores the value of the flag.
func (f *FlagSet) Bool(name string, value bool, usage string) *bool {
p := new(bool)
f.BoolVar(p, name, value, usage)
return p
}
// Bool defines a bool flag with specified name, default value, and usage string.
// The return value is the address of a bool variable that stores the value of the flag.
func Bool(name string, value bool, usage string) *bool {
return CommandLine.Bool(name, value, usage)
}
// IntVar defines an int flag with specified name, default value, and usage string.
// The argument p points to an int variable in which to store the value of the flag.
func (f *FlagSet) IntVar(p *int, name string, value int, usage string) {
f.Var(newIntValue(value, p), name, usage)
}
// IntVar defines an int flag with specified name, default value, and usage string.
// The argument p points to an int variable in which to store the value of the flag.
func IntVar(p *int, name string, value int, usage string) {
CommandLine.Var(newIntValue(value, p), name, usage)
}
// Int defines an int flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag.
func (f *FlagSet) Int(name string, value int, usage string) *int {
p := new(int)
f.IntVar(p, name, value, usage)
return p
}
// Int defines an int flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag.
func Int(name string, value int, usage string) *int {
return CommandLine.Int(name, value, usage)
}
// Int64Var defines an int64 flag with specified name, default value, and usage string.
// The argument p points to an int64 variable in which to store the value of the flag.
func (f *FlagSet) Int64Var(p *int64, name string, value int64, usage string) {
f.Var(newInt64Value(value, p), name, usage)
}
// Int64Var defines an int64 flag with specified name, default value, and usage string.
// The argument p points to an int64 variable in which to store the value of the flag.
func Int64Var(p *int64, name string, value int64, usage string) {
CommandLine.Var(newInt64Value(value, p), name, usage)
}
// Int64 defines an int64 flag with specified name, default value, and usage string.
// The return value is the address of an int64 variable that stores the value of the flag.
func (f *FlagSet) Int64(name string, value int64, usage string) *int64 {
p := new(int64)
f.Int64Var(p, name, value, usage)
return p
}
// Int64 defines an int64 flag with specified name, default value, and usage string.
// The return value is the address of an int64 variable that stores the value of the flag.
func Int64(name string, value int64, usage string) *int64 {
return CommandLine.Int64(name, value, usage)
}
// UintVar defines a uint flag with specified name, default value, and usage string.
// The argument p points to a uint variable in which to store the value of the flag.
func (f *FlagSet) UintVar(p *uint, name string, value uint, usage string) {
f.Var(newUintValue(value, p), name, usage)
}
// UintVar defines a uint flag with specified name, default value, and usage string.
// The argument p points to a uint variable in which to store the value of the flag.
func UintVar(p *uint, name string, value uint, usage string) {
CommandLine.Var(newUintValue(value, p), name, usage)
}
// Uint defines a uint flag with specified name, default value, and usage string.
// The return value is the address of a uint variable that stores the value of the flag.
func (f *FlagSet) Uint(name string, value uint, usage string) *uint {
p := new(uint)
f.UintVar(p, name, value, usage)
return p
}
// Uint defines a uint flag with specified name, default value, and usage string.
// The return value is the address of a uint variable that stores the value of the flag.
func Uint(name string, value uint, usage string) *uint {
return CommandLine.Uint(name, value, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
// The argument p points to a uint64 variable in which to store the value of the flag.
func (f *FlagSet) Uint64Var(p *uint64, name string, value uint64, usage string) {
f.Var(newUint64Value(value, p), name, usage)
}
// Uint64Var defines a uint64 flag with specified name, default value, and usage string.
// The argument p points to a uint64 variable in which to store the value of the flag.
func Uint64Var(p *uint64, name string, value uint64, usage string) {
CommandLine.Var(newUint64Value(value, p), name, usage)
}
// Uint64 defines a uint64 flag with specified name, default value, and usage string.
// The return value is the address of a uint64 variable that stores the value of the flag.
func (f *FlagSet) Uint64(name string, value uint64, usage string) *uint64 {
p := new(uint64)
f.Uint64Var(p, name, value, usage)
return p
}
// Uint64 defines a uint64 flag with specified name, default value, and usage string.
// The return value is the address of a uint64 variable that stores the value of the flag.
func Uint64(name string, value uint64, usage string) *uint64 {
return CommandLine.Uint64(name, value, usage)
}
// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func (f *FlagSet) StringVar(p *string, name string, value string, usage string) {
f.Var(newStringValue(value, p), name, usage)
}
// StringVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a string variable in which to store the value of the flag.
func StringVar(p *string, name string, value string, usage string) {
CommandLine.Var(newStringValue(value, p), name, usage)
}
// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func (f *FlagSet) String(name string, value string, usage string) *string {
p := new(string)
f.StringVar(p, name, value, usage)
return p
}
// String defines a string flag with specified name, default value, and usage string.
// The return value is the address of a string variable that stores the value of the flag.
func String(name string, value string, usage string) *string {
return CommandLine.String(name, value, usage)
}
// Float64Var defines a float64 flag with specified name, default value, and usage string.
// The argument p points to a float64 variable in which to store the value of the flag.
func (f *FlagSet) Float64Var(p *float64, name string, value float64, usage string) {
f.Var(newFloat64Value(value, p), name, usage)
}
// Float64Var defines a float64 flag with specified name, default value, and usage string.
// The argument p points to a float64 variable in which to store the value of the flag.
func Float64Var(p *float64, name string, value float64, usage string) {
CommandLine.Var(newFloat64Value(value, p), name, usage)
}
// Float64 defines a float64 flag with specified name, default value, and usage string.
// The return value is the address of a float64 variable that stores the value of the flag.
func (f *FlagSet) Float64(name string, value float64, usage string) *float64 {
p := new(float64)
f.Float64Var(p, name, value, usage)
return p
}
// Float64 defines a float64 flag with specified name, default value, and usage string.
// The return value is the address of a float64 variable that stores the value of the flag.
func Float64(name string, value float64, usage string) *float64 {
return CommandLine.Float64(name, value, usage)
}
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func (f *FlagSet) DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
f.Var(newDurationValue(value, p), name, usage)
}
// DurationVar defines a time.Duration flag with specified name, default value, and usage string.
// The argument p points to a time.Duration variable in which to store the value of the flag.
func DurationVar(p *time.Duration, name string, value time.Duration, usage string) {
CommandLine.Var(newDurationValue(value, p), name, usage)
}
// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func (f *FlagSet) Duration(name string, value time.Duration, usage string) *time.Duration {
p := new(time.Duration)
f.DurationVar(p, name, value, usage)
return p
}
// Duration defines a time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a time.Duration variable that stores the value of the flag.
func Duration(name string, value time.Duration, usage string) *time.Duration {
return CommandLine.Duration(name, value, usage)
}
// Var defines a flag with the specified name and usage string. The type and
// value of the flag are represented by the first argument, of type Value, which
// typically holds a user-defined implementation of Value. For instance, the
// caller could create a flag that turns a comma-separated string into a slice
// of strings by giving the slice the methods of Value; in particular, Set would
// decompose the comma-separated string into the slice.
func (f *FlagSet) Var(value Value, name string, usage string) {
// Remember the default value as a string; it won't change.
flag := &Flag{name, usage, value, value.String()}
_, alreadythere := f.formal[name]
if alreadythere {
fmt.Fprintf(f.out(), "%s %v redefined: %s\n", f.name, f.FlagKnownAs, name)
panic(fmt.Sprintf("%v redefinition", f.FlagKnownAs)) // Happens only if flags are declared with identical names
}
if f.formal == nil {
f.formal = make(map[string]*Flag)
}
f.formal[name] = flag
}
// Var defines a flag with the specified name and usage string. The type and
// value of the flag are represented by the first argument, of type Value, which
// typically holds a user-defined implementation of Value. For instance, the
// caller could create a flag that turns a comma-separated string into a slice
// of strings by giving the slice the methods of Value; in particular, Set would
// decompose the comma-separated string into the slice.
func Var(value Value, name string, usage string) {
CommandLine.Var(value, name, usage)
}
// failf prints to standard error a formatted error and usage message and
// returns the error.
func (f *FlagSet) failf(format string, a ...interface{}) error {
err := fmt.Errorf(format, a...)
fmt.Fprintln(f.out(), err)
f.usage()
return err
}
// usage calls the Usage method for the flag set, or the usage function if
// the flag set is CommandLine.
func (f *FlagSet) usage() {
if f.Usage == nil {
if f == CommandLine {
Usage()
} else {
defaultUsage(f)
}
} else {
f.Usage()
}
}
func (f *FlagSet) parseOne() (flagName string, long, finished bool, err error) {
if len(f.procArgs) == 0 {
finished = true
return
}
// processing previously encountered single-rune flag
if flag := f.procFlag; len(flag) > 0 {
_, n := utf8.DecodeRuneInString(flag)
f.procFlag = flag[n:]
flagName = flag[0:n]
return
}
a := f.procArgs[0]
// one non-flag argument
if a == "-" || a == "" || a[0] != '-' {
if f.allowIntersperse {
f.args = append(f.args, a)
f.procArgs = f.procArgs[1:]
return
}
f.args = append(f.args, f.procArgs...)
f.procArgs = nil
finished = true
return
}
// end of flags
if f.procArgs[0] == "--" {
f.args = append(f.args, f.procArgs[1:]...)
f.procArgs = nil
finished = true
return
}
// long flag signified with "--" prefix
if a[1] == '-' {
long = true
i := strings.Index(a, "=")
if i < 0 {
f.procArgs = f.procArgs[1:]
flagName = a[2:]
return
}
flagName = a[2:i]
if flagName == "" {
err = fmt.Errorf("empty %v in argument %q", f.FlagKnownAs, a)
return
}
f.procArgs = f.procArgs[1:]
f.procFlag = a[i:]
return
}
// some number of single-rune flags
a = a[1:]
_, n := utf8.DecodeRuneInString(a)
flagName = a[0:n]
f.procFlag = a[n:]
f.procArgs = f.procArgs[1:]
return
}
func flagWithMinus(name string) string {
if len(name) > 1 {
return "--" + name
}
return "-" + name
}
func (f *FlagSet) parseFlagArg(name string, long bool) (finished bool, err error) {
m := f.formal
flag, alreadythere := m[name] // BUG
if !alreadythere {
if name == "help" || name == "h" { // special case for nice help message.
f.usage()
ErrHelp = errors.New(fmt.Sprintf("%v: %v", f.FlagKnownAs, ErrHelp.Error()))
return false, ErrHelp
}
// TODO print --xxx when flag is more than one rune.
return false, f.failf("%v provided but not defined: %s", f.FlagKnownAs, flagWithMinus(name))
}
if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() && !strings.HasPrefix(f.procFlag, "=") {
// special case: doesn't need an arg, and an arg hasn't
// been provided explicitly.
if err := fv.Set("true"); err != nil {
return false, f.failf("invalid boolean %v %s: %v", f.FlagKnownAs, name, err)
}
} else {
// It must have a value, which might be the next argument.
var hasValue bool
var value string
if f.procFlag != "" {
// value directly follows flag
value = f.procFlag
if long {
if value[0] != '=' {
panic(fmt.Sprintf("no leading '=' in long %v", f.FlagKnownAs))
}
value = value[1:]
}
hasValue = true
f.procFlag = ""
}
if !hasValue && len(f.procArgs) > 0 {
// value is the next arg
hasValue = true
value, f.procArgs = f.procArgs[0], f.procArgs[1:]
}
if !hasValue {
return false, f.failf("%v needs an argument: %s", f.FlagKnownAs, flagWithMinus(name))
}
if err := flag.Value.Set(value); err != nil {
return false, f.failf("invalid value %q for %v %s: %v", value, f.FlagKnownAs, flagWithMinus(name), err)
}
}
if f.actual == nil {
f.actual = make(map[string]*Flag)
}
f.actual[name] = flag
return
}
// Parse parses flag definitions from the argument list, which should not
// include the command name. Must be called after all flags in the FlagSet
// are defined and before flags are accessed by the program.
// The return value will be ErrHelp if --help or -h was set but not defined.
// If allowIntersperse is set, arguments and flags can be interspersed, that
// is flags can follow positional arguments.
func (f *FlagSet) Parse(allowIntersperse bool, arguments []string) error {
f.parsed = true
f.procArgs = arguments
f.procFlag = ""
f.args = nil
f.allowIntersperse = allowIntersperse
for {
name, long, finished, err := f.parseOne()
if !finished {
if name != "" {
finished, err = f.parseFlagArg(name, long)
}
}
if err != nil {
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
os.Exit(2)
case PanicOnError:
panic(err)
}
}
if !finished {
continue
}
if err == nil {
break
}
}
return nil
}
// Parsed reports whether f.Parse has been called.
func (f *FlagSet) Parsed() bool {
return f.parsed
}
// Parse parses the command-line flags from os.Args[1:]. Must be called
// after all flags are defined and before flags are accessed by the program.
// If allowIntersperse is set, arguments and flags can be interspersed, that
// is flags can follow positional arguments.
func Parse(allowIntersperse bool) {
// Ignore errors; CommandLine is set for ExitOnError.
CommandLine.Parse(allowIntersperse, os.Args[1:])
}
// Parsed returns true if the command-line flags have been parsed.
func Parsed() bool {
return CommandLine.Parsed()
}
// CommandLine is the default set of command-line flags, parsed from os.Args.
// The top-level functions such as BoolVar, Arg, and so on are wrappers for the
// methods of CommandLine.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// NewFlagSet returns a new, empty flag set with the specified name and
// error handling property.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
return NewFlagSetWithFlagKnownAs(name, errorHandling, "flag")
}
// NewFlagSetWithFlagKnownAs returns a new, empty flag set with the specified name and
// error handling property. All error messages and other references to the
// individual flags will use aka, for e.g. if aka = 'option', the message will be
// 'value for option' not 'value for flag'.
func NewFlagSetWithFlagKnownAs(name string, errorHandling ErrorHandling, aka string) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
FlagKnownAs: aka,
}
return f
}
// Init sets the name and error handling property for a flag set.
// By default, the zero FlagSet uses an empty name and the
// ContinueOnError error handling policy.
func (f *FlagSet) Init(name string, errorHandling ErrorHandling) {
f.name = name
f.errorHandling = errorHandling
}