diff --git a/.gitignore b/.gitignore index 2aabe44..7399d0d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,6 @@ build/ */Makefile.* core* !core*.sh +!core*.go *~ .build/ diff --git a/.travis.yml b/.travis.yml index 2a6365e..16991a2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -84,6 +84,9 @@ script: - | cd ${TRAVIS_BUILD_DIR}/libcalends go build -o libcalends.so -buildmode=c-shared . + - | + cd ${TRAVIS_BUILD_DIR}/cli + go build -o calends . after_failure: - ${TRAVIS_BUILD_DIR}/tests/core_dump_info.sh /tmp/ @@ -99,13 +102,13 @@ after_success: go get github.com/aktau/github-release github-release info --user danhunsaker --repo calends --tag ${TRAVIS_TAG} || \ github-release release --user danhunsaker --repo calends --tag ${TRAVIS_TAG} --draft - for archive in $(find dist/ -iname '*.tgz') + for archive in $(find dist/ -type f -name '*.tgz' -o -type f -name 'calends-*') do github-release upload \ --user danhunsaker \ --repo calends \ --tag ${TRAVIS_TAG} \ - --name "${archive#dist/}" \ + --name "$(echo ${archive} | sed 's:dist/(bin/)?::;s/windows/win/;s/win-4.0/win-nt/;s/win-6.0/win-vista/;s/win-6.1/win-7/;s/win-6.3/win-8.1/;s/win-10.0/win-10/;s/darwin/mac-os/')" \ --file "${archive}" done fi diff --git a/build-all b/build-all index a484e92..bc207a6 100755 --- a/build-all +++ b/build-all @@ -11,8 +11,9 @@ unset v go get github.com/karalabe/xgo ${GOPATH}/bin/xgo -buildmode=c-shared -dest=$(pwd)/dist/ -out=libcalends-${VERSION} -targets=${TARGETS} github.com/danhunsaker/calends/libcalends +${GOPATH}/bin/xgo -dest=$(pwd)/dist/bin/ -out=calends-${VERSION} -targets="*/*,${TARGETS}" github.com/danhunsaker/calends/cli -for h in $(find dist/ -iname '*.h') +for h in $(find dist/ -name '*.h') do dir=${h/.h/} mkdir -p ${dir} diff --git a/calends.go b/calends.go index 5602a85..205e2c2 100644 --- a/calends.go +++ b/calends.go @@ -41,7 +41,7 @@ type Calends struct { } // Version of the library -var Version = "0.0.4" +var Version = "0.0.5" // Create is the mechanism for constructing new Calends objects. /* diff --git a/cli/batch/abuts.go b/cli/batch/abuts.go new file mode 100644 index 0000000..cb261b8 --- /dev/null +++ b/cli/batch/abuts.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "abuts", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["abuts"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: abuts ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.Abuts(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/add-from-end.go b/cli/batch/add-from-end.go new file mode 100644 index 0000000..2d9e37d --- /dev/null +++ b/cli/batch/add-from-end.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "add-from-end", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["add-from-end"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: add-from-end ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.AddFromEnd(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/add.go b/cli/batch/add.go new file mode 100644 index 0000000..06ff977 --- /dev/null +++ b/cli/batch/add.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "add", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["add"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: add ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.Add(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/compare.go b/cli/batch/compare.go new file mode 100644 index 0000000..a3655f3 --- /dev/null +++ b/cli/batch/compare.go @@ -0,0 +1,45 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "compare", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(func(arg string) []string { + return []string{"start", "end", "start-end", "end-start", "duration"} + }), + ), + ), + ), + ) + + commands["compare"] = func(args []string) error { + if len(args) != 3 { + return errors.New("usage: compare ") + } + + source, compare, mode := args[0], args[1], args[2] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%d\n", stamp1.Compare(stamp2, mode)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/contains.go b/cli/batch/contains.go new file mode 100644 index 0000000..a02e4ef --- /dev/null +++ b/cli/batch/contains.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "contains", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["contains"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: contains ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.Contains(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/core.go b/cli/batch/core.go new file mode 100644 index 0000000..e2dbed4 --- /dev/null +++ b/cli/batch/core.go @@ -0,0 +1,100 @@ +package batch + +import ( + "fmt" + "io" + "log" + "strings" + + "github.com/chzyer/readline" + "github.com/danhunsaker/calends" + "github.com/danhunsaker/calends/calendars" + "github.com/mattn/go-shellwords" + "github.com/urfave/cli" +) + +// Collections +var completions []readline.PrefixCompleterInterface +var commands = make(map[string]func([]string) error) +var state = make(map[string]calends.Calends) + +// Helper functions +var printf func(string, ...interface{}) // Need the readline session to properly define this one +var completionCalendarList = func(arg string) []string { + return calendars.ListRegistered() +} +var completionStatesList = func(arg string) (list []string) { + for name := range state { + list = append(list, name) + } + return +} + +// Main logic +var Console = func(c *cli.Context) error { + completions = append( + completions, + readline.PcItem("help"), + readline.PcItem("exit"), + readline.PcItem("quit"), + ) + completer := readline.NewPrefixCompleter(completions...) + shellwords.ParseEnv = true + shellwords.ParseBacktick = true + + l, err := readline.NewEx(&readline.Config{ + Prompt: "\033[35mcalends \033[36m» \033[0m", + HistoryFile: "", + AutoComplete: completer, + InterruptPrompt: "^C", + EOFPrompt: "exit", + HistorySearchFold: true, + }) + if err != nil { + panic(err) + } + defer l.Close() + + // Normally this helper would be defined entirely outside the main logic + // function, but we need the readline session, first, to make it work... + printf = func(message string, args ...interface{}) { + io.WriteString(l.Stdout(), fmt.Sprintf(message, args...)) + } + + log.SetOutput(l.Stderr()) + for { + line, err := l.Readline() + if err == readline.ErrInterrupt { + if len(line) == 0 { + break + } else { + continue + } + } else if err == io.EOF { + break + } + + line = strings.TrimSpace(line) + + if line == "" || line == "help" { + printf("commands:\n") + printf(completer.Tree(" ")) + continue + } else if line == "quit" || line == "exit" { + break + } + + args, err := shellwords.Parse(line) + if err != nil { + log.Printf("%v\n", err) + } else if cmd, ok := commands[args[0]]; ok { + err = cmd(args[1:]) + if err != nil { + log.Printf("%v\n", err) + } + } else { + log.Printf("unknown command %q\n", args[0]) + } + } + return nil +} diff --git a/cli/batch/date.go b/cli/batch/date.go new file mode 100644 index 0000000..19513d4 --- /dev/null +++ b/cli/batch/date.go @@ -0,0 +1,38 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "date", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["date"] = func(args []string) error { + if len(args) != 3 { + return errors.New("usage: date ") + } + + calendar, format, source := args[0], args[1], args[2] + + if stamp, ok := state[source]; ok { + date, err := stamp.Date(calendar, format) + if err != nil { + return err + } + + printf("%s\n", date) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/difference.go b/cli/batch/difference.go new file mode 100644 index 0000000..ef7b0b8 --- /dev/null +++ b/cli/batch/difference.go @@ -0,0 +1,46 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "difference", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(func(arg string) []string { + return []string{"start", "end", "start-end", "end-start", "duration"} + }), + ), + ), + ), + ) + + commands["difference"] = func(args []string) error { + if len(args) != 3 { + return errors.New("usage: difference ") + } + + source, compare, mode := args[0], args[1], args[2] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + diff := stamp1.Difference(stamp2, mode) + printf("%s\n", diff.Text('f', -6)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/end-date.go b/cli/batch/end-date.go new file mode 100644 index 0000000..43c1872 --- /dev/null +++ b/cli/batch/end-date.go @@ -0,0 +1,38 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "end-date", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["end-date"] = func(args []string) error { + if len(args) != 3 { + return errors.New("usage: end-date ") + } + + calendar, format, source := args[0], args[1], args[2] + + if stamp, ok := state[source]; ok { + date, err := stamp.EndDate(calendar, format) + if err != nil { + return err + } + + printf("%s\n", date) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/ends-after.go b/cli/batch/ends-after.go new file mode 100644 index 0000000..278d9e9 --- /dev/null +++ b/cli/batch/ends-after.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "ends-after", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["ends-after"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: ends-after ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.EndsAfter(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/ends-before.go b/cli/batch/ends-before.go new file mode 100644 index 0000000..1dcfcf6 --- /dev/null +++ b/cli/batch/ends-before.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "ends-before", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["ends-before"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: ends-before ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.EndsBefore(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/ends-during.go b/cli/batch/ends-during.go new file mode 100644 index 0000000..0b1d41f --- /dev/null +++ b/cli/batch/ends-during.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "ends-during", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["ends-during"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: ends-during ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.EndsDuring(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/gap.go b/cli/batch/gap.go new file mode 100644 index 0000000..1153445 --- /dev/null +++ b/cli/batch/gap.go @@ -0,0 +1,47 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "gap", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["gap"] = func(args []string) error { + var err error + + if len(args) != 3 { + return errors.New("usage: gap ") + } + + source, combine, target := args[0], args[1], args[2] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[combine]; ok { + state[target], err = stamp1.Gap(stamp2) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + combine + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/intersect.go b/cli/batch/intersect.go new file mode 100644 index 0000000..d3b88c8 --- /dev/null +++ b/cli/batch/intersect.go @@ -0,0 +1,47 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "intersect", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["intersect"] = func(args []string) error { + var err error + + if len(args) != 3 { + return errors.New("usage: intersect ") + } + + source, combine, target := args[0], args[1], args[2] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[combine]; ok { + state[target], err = stamp1.Intersect(stamp2) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + combine + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-after.go b/cli/batch/is-after.go new file mode 100644 index 0000000..198e2d1 --- /dev/null +++ b/cli/batch/is-after.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-after", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-after"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-after ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsAfter(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-before.go b/cli/batch/is-before.go new file mode 100644 index 0000000..14aa676 --- /dev/null +++ b/cli/batch/is-before.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-before", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-before"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-before ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsBefore(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-during.go b/cli/batch/is-during.go new file mode 100644 index 0000000..f3a9e3f --- /dev/null +++ b/cli/batch/is-during.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-during", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-during"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-during ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsDuring(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-longer.go b/cli/batch/is-longer.go new file mode 100644 index 0000000..0556d1e --- /dev/null +++ b/cli/batch/is-longer.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-longer", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-longer"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-longer ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsLonger(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-same-duration.go b/cli/batch/is-same-duration.go new file mode 100644 index 0000000..1699d81 --- /dev/null +++ b/cli/batch/is-same-duration.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-same-duration", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-same-duration"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-same-duration ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsSameDuration(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-same.go b/cli/batch/is-same.go new file mode 100644 index 0000000..e561ff6 --- /dev/null +++ b/cli/batch/is-same.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-same", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-same"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-same ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsSame(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/is-shorter.go b/cli/batch/is-shorter.go new file mode 100644 index 0000000..fef1002 --- /dev/null +++ b/cli/batch/is-shorter.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "is-shorter", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["is-shorter"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: is-shorter ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.IsShorter(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/merge.go b/cli/batch/merge.go new file mode 100644 index 0000000..b82c713 --- /dev/null +++ b/cli/batch/merge.go @@ -0,0 +1,47 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "merge", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["merge"] = func(args []string) error { + var err error + + if len(args) != 3 { + return errors.New("usage: merge ") + } + + source, combine, target := args[0], args[1], args[2] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[combine]; ok { + state[target], err = stamp1.Merge(stamp2) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + combine + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/next.go b/cli/batch/next.go new file mode 100644 index 0000000..1263837 --- /dev/null +++ b/cli/batch/next.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "next", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["next"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: next ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.Next(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/overlaps.go b/cli/batch/overlaps.go new file mode 100644 index 0000000..bf4706c --- /dev/null +++ b/cli/batch/overlaps.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "overlaps", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["overlaps"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: overlaps ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.Overlaps(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/parse-range.go b/cli/batch/parse-range.go new file mode 100644 index 0000000..7c5d50e --- /dev/null +++ b/cli/batch/parse-range.go @@ -0,0 +1,37 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" + "github.com/danhunsaker/calends" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "parse-range", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["parse-range"] = func(args []string) error { + var err error + + if len(args) != 5 { + return errors.New("usage: parse-range ") + } + + calendar, format, date, end, target := args[0], args[1], args[2], args[3], args[4] + + state[target], err = calends.Create(map[string]interface{}{"start": date, "end": end}, calendar, format) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + + return nil + } +} diff --git a/cli/batch/parse.go b/cli/batch/parse.go new file mode 100644 index 0000000..c8e949a --- /dev/null +++ b/cli/batch/parse.go @@ -0,0 +1,37 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" + "github.com/danhunsaker/calends" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "parse", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["parse"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: parse ") + } + + calendar, format, date, target := args[0], args[1], args[2], args[3] + + state[target], err = calends.Create(date, calendar, format) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + + return nil + } +} diff --git a/cli/batch/previous.go b/cli/batch/previous.go new file mode 100644 index 0000000..9d7dd79 --- /dev/null +++ b/cli/batch/previous.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "previous", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["previous"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: previous ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.Previous(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/set-date.go b/cli/batch/set-date.go new file mode 100644 index 0000000..9d7eb5b --- /dev/null +++ b/cli/batch/set-date.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "set-date", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["set-date"] = func(args []string) error { + var err error + + if len(args) != 5 { + return errors.New("usage: set-date ") + } + + calendar, format, date, source, target := args[0], args[1], args[2], args[3], args[4] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.SetDate(date, calendar, format) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/set-end-date.go b/cli/batch/set-end-date.go new file mode 100644 index 0000000..3f70357 --- /dev/null +++ b/cli/batch/set-end-date.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "set-end-date", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["set-end-date"] = func(args []string) error { + var err error + + if len(args) != 5 { + return errors.New("usage: set-end-date ") + } + + calendar, format, date, source, target := args[0], args[1], args[2], args[3], args[4] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.SetEndDate(date, calendar, format) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/starts-after.go b/cli/batch/starts-after.go new file mode 100644 index 0000000..e29aed7 --- /dev/null +++ b/cli/batch/starts-after.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "starts-after", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["starts-after"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: starts-after ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.StartsAfter(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/starts-before.go b/cli/batch/starts-before.go new file mode 100644 index 0000000..7c8943d --- /dev/null +++ b/cli/batch/starts-before.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "starts-before", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["starts-before"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: starts-before ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.StartsBefore(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/starts-during.go b/cli/batch/starts-during.go new file mode 100644 index 0000000..948e456 --- /dev/null +++ b/cli/batch/starts-during.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "starts-during", + readline.PcItemDynamic( + completionStatesList, + readline.PcItemDynamic(completionStatesList), + ), + ), + ) + + commands["starts-during"] = func(args []string) error { + if len(args) != 2 { + return errors.New("usage: starts-during ") + } + + source, compare := args[0], args[1] + + if stamp1, ok := state[source]; ok { + if stamp2, ok := state[compare]; ok { + printf("%t\n", stamp1.StartsDuring(stamp2)) + } else { + return errors.New("timestamp '" + compare + "' not set") + } + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/subtract-from-end.go b/cli/batch/subtract-from-end.go new file mode 100644 index 0000000..e5d6dbc --- /dev/null +++ b/cli/batch/subtract-from-end.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "subtract-from-end", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["subtract-from-end"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: subtract-from-end ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.SubtractFromEnd(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/batch/subtract.go b/cli/batch/subtract.go new file mode 100644 index 0000000..eb36c22 --- /dev/null +++ b/cli/batch/subtract.go @@ -0,0 +1,40 @@ +package batch + +import ( + "errors" + + "github.com/chzyer/readline" +) + +func init() { + completions = append( + completions, + readline.PcItem( + "subtract", + readline.PcItemDynamic(completionCalendarList), + ), + ) + + commands["subtract"] = func(args []string) error { + var err error + + if len(args) != 4 { + return errors.New("usage: subtract ") + } + + calendar, offset, source, target := args[0], args[1], args[2], args[3] + + if stamp, ok := state[source]; ok { + state[target], err = stamp.Subtract(offset, calendar) + if err != nil { + return err + } + + printf("%s = %s\n", target, state[target]) + } else { + return errors.New("timestamp '" + source + "' not set") + } + + return nil + } +} diff --git a/cli/calends.go b/cli/calends.go new file mode 100644 index 0000000..ee906e4 --- /dev/null +++ b/cli/calends.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/danhunsaker/calends" + "github.com/danhunsaker/calends/cli/batch" + "github.com/urfave/cli" +) + +var commands []cli.Command + +var ( + errArgMismatch = cli.NewExitError("Incorrect argument count\n", 1) +) + +func main() { + app := cli.NewApp() + + // metadata + app.Name = "calends" + app.Usage = "manipulate dates and times in multiple calendar systems at once" + app.Version = calends.Version + app.Compiled = time.Now() + app.Authors = []cli.Author{ + cli.Author{ + Name: "Dan Hunsaker", + Email: "danhunsaker@gmail.com", + }, + } + app.Copyright = "(c) 2018 Dan Hunsaker" + + // configuration + app.EnableBashCompletion = true + app.Commands = commands + app.ExitErrHandler = handleExitError + app.Action = batch.Console + + // Make it go!!!! + app.Run(os.Args) +} + +// replicated and adapted from cli package... +func handleExitError(context *cli.Context, err error) { + if err == nil { + return + } + + if exitErr, ok := err.(cli.ExitCoder); ok { + if err.Error() != "" { + if _, ok := exitErr.(cli.ErrorFormatter); ok { + fmt.Fprintf(cli.ErrWriter, "%+v\n", err) + } else { + fmt.Fprintln(cli.ErrWriter, err) + } + } + if exitErr.ExitCode() == 1 { + cli.ShowCommandHelp(context, context.Command.Name) + } + cli.OsExiter(exitErr.ExitCode()) + return + } + + if multiErr, ok := err.(cli.MultiError); ok { + code, isUsageError := handleMultiError(multiErr) + if isUsageError { + cli.ShowCommandHelp(context, context.Command.Name) + } + cli.OsExiter(code) + return + } +} + +func handleMultiError(multiErr cli.MultiError) (int, bool) { + code := 127 + isUsageError := false + for _, merr := range multiErr.Errors { + if multiErr2, ok := merr.(cli.MultiError); ok { + code, isUsageError = handleMultiError(multiErr2) + } else { + fmt.Fprintln(cli.ErrWriter, merr) + if exitErr, ok := merr.(cli.ExitCoder); ok { + if exitErr.ExitCode() == 1 { + isUsageError = true + } + code = exitErr.ExitCode() + } + } + } + return code, isUsageError +} diff --git a/cli/compare.go b/cli/compare.go new file mode 100644 index 0000000..8c66c1a --- /dev/null +++ b/cli/compare.go @@ -0,0 +1,109 @@ +package main + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "os" + + "github.com/danhunsaker/calends" + "github.com/urfave/cli" +) + +func init() { + commands = append(commands, []cli.Command{ + cli.Command{ + Name: "compare", + Usage: "compare two timestamps using a given comparison method", + ArgsUsage: " [ []]", + + Action: func(c *cli.Context) error { + if c.NArg() < 1 || c.NArg() > 3 { + return errArgMismatch + } + + method := c.Args()[0] + stamp1 := "" + if c.NArg() > 1 { + stamp1 = c.Args()[1] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + stamp1 = fmt.Sprintf("%s%s", stamp1, scanner.Text()) + } + } + stamp2 := "" + if c.NArg() == 3 { + stamp2 = c.Args()[2] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + stamp2 = fmt.Sprintf("%s%s", stamp2, scanner.Text()) + } + } + + var moment1 calends.Calends + err := json.Unmarshal([]byte(stamp1), &moment1) + if err != nil { + return cli.NewExitError(err, 2) + } + + var moment2 calends.Calends + err = json.Unmarshal([]byte(stamp2), &moment2) + if err != nil { + return cli.NewExitError(err, 2) + } + + output, err := callComparisonMethod(method, moment1, moment2) + if err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Printf("%v\n", output) + return nil + }, + }, + }...) +} + +func callComparisonMethod(method string, moment1, moment2 calends.Calends) (ret bool, err error) { + switch method { + case "contains": + ret = moment1.Contains(moment2) + case "overlaps": + ret = moment1.Overlaps(moment2) + case "abuts": + ret = moment1.Abuts(moment2) + case "same": + ret = moment1.IsSame(moment2) + case "shorter": + ret = moment1.IsShorter(moment2) + case "same-duration": + ret = moment1.IsSameDuration(moment2) + case "longer": + ret = moment1.IsLonger(moment2) + case "before": + ret = moment1.IsBefore(moment2) + case "start-before": + ret = moment1.StartsBefore(moment2) + case "end-before": + ret = moment1.EndsBefore(moment2) + case "during": + ret = moment1.IsDuring(moment2) + case "start-during": + ret = moment1.StartsDuring(moment2) + case "end-during": + ret = moment1.EndsDuring(moment2) + case "after": + ret = moment1.IsAfter(moment2) + case "start-after": + ret = moment1.StartsAfter(moment2) + case "end-after": + ret = moment1.EndsAfter(moment2) + default: + err = errors.New("Unsupported comparison method") + } + + return +} diff --git a/cli/convert.go b/cli/convert.go new file mode 100644 index 0000000..6bad26f --- /dev/null +++ b/cli/convert.go @@ -0,0 +1,53 @@ +package main + +import ( + "bufio" + "fmt" + "os" + + "github.com/danhunsaker/calends" + "github.com/urfave/cli" +) + +func init() { + commands = append(commands, []cli.Command{ + cli.Command{ + Name: "convert", + Usage: "convert a date/time from one calendar and format to another", + ArgsUsage: " []", + + Action: func(c *cli.Context) error { + if c.NArg() < 4 || c.NArg() > 5 { + return errArgMismatch + } + + in_cal := c.Args()[0] + in_form := c.Args()[1] + out_cal := c.Args()[2] + out_form := c.Args()[3] + date := "" + if c.NArg() == 5 { + date = c.Args()[4] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + date = fmt.Sprintf("%s%s", date, scanner.Text()) + } + } + + moment, err := calends.Create(date, in_cal, in_form) + if err != nil { + return cli.NewExitError(err, 2) + } + + output, err := moment.Date(out_cal, out_form) + if err != nil { + return cli.NewExitError(err, 2) + } + + fmt.Println(output) + return nil + }, + }, + }...) +} diff --git a/cli/format.go b/cli/format.go new file mode 100644 index 0000000..8e62fb4 --- /dev/null +++ b/cli/format.go @@ -0,0 +1,53 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + + "github.com/danhunsaker/calends" + "github.com/urfave/cli" +) + +func init() { + commands = append(commands, []cli.Command{ + cli.Command{ + Name: "format", + Usage: "format a timestamp in a given calendar system and format", + ArgsUsage: " []", + + Action: func(c *cli.Context) error { + if c.NArg() < 2 || c.NArg() > 3 { + return errArgMismatch + } + + out_cal := c.Args()[0] + out_form := c.Args()[1] + stamp := "" + if c.NArg() == 3 { + stamp = c.Args()[2] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + stamp = fmt.Sprintf("%s%s", stamp, scanner.Text()) + } + } + + var moment calends.Calends + err := json.Unmarshal([]byte(stamp), &moment) + if err != nil { + return cli.NewExitError(err, 2) + } + + output, err := moment.Date(out_cal, out_form) + if err != nil { + return cli.NewExitError(err, 2) + } + + fmt.Println(output) + return nil + }, + }, + }...) +} diff --git a/cli/offset.go b/cli/offset.go new file mode 100644 index 0000000..a16dfc8 --- /dev/null +++ b/cli/offset.go @@ -0,0 +1,71 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + + "github.com/danhunsaker/calends" + "github.com/urfave/cli" +) + +func init() { + commands = append(commands, []cli.Command{ + cli.Command{ + Name: "offset", + Usage: "adjusts a timestamp by an offset in a given calendar", + ArgsUsage: " [ []]", + + Action: func(c *cli.Context) error { + if c.NArg() < 1 || c.NArg() > 3 { + return errArgMismatch + } + + off_cal := c.Args()[0] + offset := "" + if c.NArg() > 1 { + offset = c.Args()[1] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + offset = fmt.Sprintf("%s%s", offset, scanner.Text()) + } + } + stamp := "" + if c.NArg() == 3 { + stamp = c.Args()[2] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + stamp = fmt.Sprintf("%s%s", stamp, scanner.Text()) + } + } + + var moment calends.Calends + err := json.Unmarshal([]byte(stamp), &moment) + if err != nil { + return cli.NewExitError(err, 2) + } + + moment, err = moment.Add(offset, off_cal) + if err != nil { + return cli.NewExitError(err, 2) + } + + moment, err = moment.SetDuration("0", "tai64") + if err != nil { + return cli.NewExitError(err, 2) + } + + output, err := json.Marshal(moment) + if err != nil { + return cli.NewExitError(err, 2) + } + + fmt.Println(string(output)) + return nil + }, + }, + }...) +} diff --git a/cli/parse.go b/cli/parse.go new file mode 100644 index 0000000..9e70259 --- /dev/null +++ b/cli/parse.go @@ -0,0 +1,52 @@ +package main + +import ( + "bufio" + "encoding/json" + "fmt" + "os" + + "github.com/danhunsaker/calends" + "github.com/urfave/cli" +) + +func init() { + commands = append(commands, []cli.Command{ + cli.Command{ + Name: "parse", + Usage: "parse a date/time given a calendar system and format", + ArgsUsage: " []", + + Action: func(c *cli.Context) error { + if c.NArg() < 2 || c.NArg() > 3 { + return errArgMismatch + } + + in_cal := c.Args()[0] + in_form := c.Args()[1] + date := "" + if c.NArg() == 3 { + date = c.Args()[2] + } else { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + date = fmt.Sprintf("%s%s", date, scanner.Text()) + } + } + + moment, err := calends.Create(date, in_cal, in_form) + if err != nil { + return cli.NewExitError(err, 2) + } + + output, err := json.Marshal(moment) + if err != nil { + return cli.NewExitError(err, 2) + } + + fmt.Println(string(output)) + return nil + }, + }, + }...) +} diff --git a/dist/bin/.gitignore b/dist/bin/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/dist/bin/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/docs/c/installation.rst b/docs/c/installation.rst index 79c5269..b58bb88 100644 --- a/docs/c/installation.rst +++ b/docs/c/installation.rst @@ -18,7 +18,10 @@ architecture! Source Install -------------- -To install from source, you'll need Golang installed to use its compiler. Clone the repository, build ``libcalends``, then copy the resulting ``.so``/``.dll`` and ``.h`` files to wherever your C/C++ compiler expects to find them. +To install from source, you'll need Golang 1.9+ installed to use its compiler. +Clone the repository, build ``libcalends``, then copy the resulting +``.so``/``.dll`` and ``.h`` files to wherever your C/C++ compiler expects to +find them. .. code-block:: bash @@ -27,6 +30,7 @@ To install from source, you'll need Golang installed to use its compiler. Clone cd $GOPATH/src/github.com/danhunsaker git clone https://github.com/danhunsaker/calends cd calends/libcalends + go get ../... go build -v -i -buildmode=c-shared -o libcalends.so Adjust the above example commands as needed for your actual development OS. diff --git a/docs/cli/installation.rst b/docs/cli/installation.rst new file mode 100644 index 0000000..6898e18 --- /dev/null +++ b/docs/cli/installation.rst @@ -0,0 +1,22 @@ +.. _installation-cli: + +.. index:: + pair: installation; CLI + +Installing Calends for the Command Line +======================================= + +You can grab a ``calends`` binary for your platform OS/architecture from the +`GitHub Releases page `_, and +just run it directly. Alternately, you can clone the source and build it from +there: + +.. code-block:: bash + + # Sample Linux steps: + mkdir -p $GOPATH/src/github.com/danhunsaker + cd $GOPATH/src/github.com/danhunsaker + git clone https://github.com/danhunsaker/calends + cd calends + go get ./... + go build -o calends ./cli diff --git a/docs/cli/usage.rst b/docs/cli/usage.rst new file mode 100644 index 0000000..50e328c --- /dev/null +++ b/docs/cli/usage.rst @@ -0,0 +1,511 @@ +.. _usage-cli: + +.. index:: + pair: usage; CLI + +Using Calends from the Command Line +=================================== + +Calends can be used from the command line directly, though some if its features +are limited or unavailable. Specifically, it doesn't support custom calendars, +so you'll need to ensure you build it with the calendar you want already loaded. + +Command Line Options +-------------------- + +.. program:: calends + +The available options for ``calends``, on the command line directly, are the +following: + +.. option:: convert [] + + :- from-calendar: The calendar system to parse the date/time with. + :- from-calendar: The format the date/time is expected to use. + :- to-calendar: The calendar system to format the date/time with. + :- to-calendar: The format the date/time is expected to use. + :- date: The value to convert. + + Converts a date from one calendar/format to another. If ``date`` isn't + provided in the arguments, it's read from ``/dev/stdin`` instead. + +.. option:: parse [] + + :- from-calendar: The calendar system to parse the date/time with. + :- from-calendar: The format the date/time is expected to use. + :- date: The value to parse. + + Converts a date from the given calendar/format to a portable/unambiguous date + stamp. The output from this command can then be used as input to others. + +.. option:: format [] + + :- to-calendar: The calendar system to format the date/time with. + :- to-calendar: The format the date/time is expected to use. + :- stamp: The value to format. + + Converts a date stamp from the :option:`parse` command to the given + caledar/format. + +.. option:: offset [ []] + + :- offset-calendar: The calendar system to interpret the offset with. + :- offset: The offset to add. + :- stamp: The value to add the offset to. + + Adds an offset to the date stamp from the :option:`parse` command. + +.. program:: calends compare + +There is also a ``calends compare``, whose options are these: + +.. option:: contains [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` contains + ``stamp2``. + +.. option:: overlaps [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` overlaps + with ``stamp2``. + +.. option:: abuts [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` abuts + ``stamp2``. + +.. option:: same [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is the same + as ``stamp2``. + +.. option:: shorter [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is shorter + than ``stamp2``. + +.. option:: same-duration [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is the same + duration as ``stamp2``. + +.. option:: longer [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is longer + than ``stamp2``. + +.. option:: before [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is before + ``stamp2``. + +.. option:: start-before [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` starts + before ``stamp2``. + +.. option:: end-before [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` ends before + ``stamp2``. + +.. option:: during [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is during + ``stamp2``. + +.. option:: start-during [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` starts + during ``stamp2``. + +.. option:: end-during [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` ends during + ``stamp2``. + +.. option:: after [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` is after + ``stamp2``. + +.. option:: start-after [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` starts + after ``stamp2``. + +.. option:: end-after [ []] + + :- stamp1: The value to compare. + :- stamp2: The value to compare the other to. + + Compares ``stamp1`` to ``stamp2``, and returns whether ``stamp1`` ends after + ``stamp2``. + +Interactive/Batch Mode +---------------------- + +Create +++++++ + +.. program:: calends (batch-mode) + +.. option:: parse + + :- calendar: The calendar system to parse the date/time with. + :- format: The format the date/time is expected to use. + :- date: The value to parse. + :- target: The name to give the result. + + Creates a new ``Calends`` value, using ``calendar`` to select a calendar + system, and ``format`` to describe the contents of ``date`` to parse. The + result is stored as ``target``, so it can be used later on by other commands. + +.. option:: parse-range + + :- calendar: The calendar system to parse the dates/times with. + :- format: The format the dates/times are expected to use. + :- date: The start date to parse. + :- end-date: The end date to parse. + :- target: The name to give the result. + + Creates a new ``Calends`` value, using ``calendar`` to select a calendar + system, and ``format`` to describe the contents of ``date`` and ``end-date`` + to parse. The result is stored as ``target``, so it can be used later on by + other commands. + +Read +++++ + +.. option:: date + + :- calendar: The calendar system to format the date/time with. + :- format: The format the date/time is expected to be in. + :- source: The name of the ``Calends`` value to use. + + Retrieves the start date of ``source`` as a string. The value is generated by + the calendar system given in ``calendar``, according to the format string in + ``format``. + +.. option:: end-date + + :- calendar: The calendar system to format the date/time with. + :- format: The format the date/time is expected to be in. + :- source: The name of the ``Calends`` value to use. + + Retrieves the end date of ``source`` as a string. The value is generated by + the calendar system given in ``calendar``, according to the format string in + ``format``. + +Update +++++++ + +.. option:: set-date + + :- calendar: The calendar system to parse the date/time with. + :- format: The format the date/time is expected to use. + :- date: The value to parse the date/time from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Sets the start date of ``target`` based on ``source``'s current value. The + inputs are the same as for :option:`parse`, above. + +.. option:: set-end-date + + :- calendar: The calendar system to parse the date/time with. + :- format: The format the date/time is expected to use. + :- date: The value to parse the date/time from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Sets the end date of ``target`` based on ``source``'s current value. The + inputs are the same as for :option:`parse`, above. + +Manipulate +++++++++++ + +.. option:: add + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Increases the end date of ``source``'s current value by ``offset``, as + interpreted by the calendar system given in ``calendar``. + +.. option:: add-from-end + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Increases the start date of ``source``'s current value by ``offset``, as + interpreted by the calendar system given in ``calendar``. + +.. option:: subtract + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Works the same as :option:`add`, except it decreases the start date, rather + than increasing it. + +.. option:: subtract-from-end + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Works the same as :option:`add-from-end`, except it decreases the end date, + rather than increasing it. + +.. option:: next + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Sets ``target`` to a ``Calends`` value of ``offset`` duration (as interpreted + by the calendar system given in ``calendar``), which abuts the end of + ``source``. If ``offset`` is empty, ``calendar`` is ignored, and ``source``'s + duration is used instead. + +.. option:: previous + + :- calendar: The calendar system to parse the offset with. + :- offset: The value to parse the offset from. + :- source: The name of the ``Calends`` value to use. + :- target: The name to give the result. + + Sets ``target`` to a ``Calends`` value of ``offset`` duration (as interpreted + by the calendar system given in ``calendar``), which abuts the start of + ``source``. If ``offset`` is empty, ``calendar`` is ignored, and ``source``'s + duration is used instead. + +Combine ++++++++ + +.. option:: merge + + :- source: The name of the ``Calends`` value to use. + :- combine: The ``Calends`` value to merge. + :- target: The name to give the result. + + Sets ``target`` to a value spanning from the earliest start date to the + latest end date between ``source`` and ``combine``. + +.. option:: intersect + + :- source: The name of the ``Calends`` value to use. + :- combine: The ``Calends`` value to intersect. + :- target: The name to give the result. + + Sets ``target`` to a value spanning the overlap between ``source`` and + ``combine``. If ``source`` and ``combine`` don't overlap, returns an error. + +.. option:: gap + + :- source: The name of the ``Calends`` value to use. + :- combine: The ``Calends`` value to gap. + :- target: The name to give the result. + + Sets ``target`` to a value spanning the gap between ``source`` and + ``combine``. If ``source`` and ``combine`` overlap (and there is, therefore, + no gap to return), returns an error. + +Compare ++++++++ + +.. option:: difference + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + :- mode: The comparison mode. + + Returns the difference of ``source`` minus ``compare``, using ``mode`` to + select which values to use in the calculation. Valid ``mode``\ s include: + + - ``start`` - use the start date of both ``source`` and ``compare`` + - ``duration`` - use the duration of both ``source`` and ``compare`` + - ``end`` - use the end date of both ``source`` and ``compare`` + - ``start-end`` - use the start of ``source``, and the end of ``compare`` + - ``end-start`` - use the end of ``source``, and the start of ``compare`` + +.. option:: compare + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + :- mode: The comparison mode. + + Returns ``-1`` if ``source`` is less than ``compare``, ``0`` if they are + equal, and ``1`` if ``source`` is greater than ``compare``, using ``mode`` to + select which values to use in the comparison. Valid ``mode``\ s are the same + as for :option:`difference`, above. + +.. option:: contains + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether ``source`` contains all of ``compare``. + +.. option:: overlaps + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether ``source`` overlaps with ``compare``. + +.. option:: abuts + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether ``source`` abuts ``compare`` (that is, whether one begins at + the same instant the other ends). + +.. option:: is-same + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether ``source`` covers the same span of time as ``compare``. + +.. option:: is-shorter + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the duration of ``source`` and ``compare``. + +.. option:: is-same-duration + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the duration of ``source`` and ``compare``. + +.. option:: is-longer + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the duration of ``source`` and ``compare``. + +.. option:: is-before + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the entirety of ``source`` with the start date of ``compare``. + +.. option:: starts-before + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the start date of ``source`` with the start date of ``compare``. + +.. option:: ends-before + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the end date of ``source`` with the start date of ``compare``. + +.. option:: is-during + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether the entirety of ``source`` lies between the start and end + dates of ``compare``. + +.. option:: starts-during + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether the start date of ``source`` lies between the start and end + dates of ``compare``. + +.. option:: ends-during + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Checks whether the end date of ``source`` lies between the start and end + dates of ``compare``. + +.. option:: is-after + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the entirety of ``source`` with the end date of ``compare``. + +.. option:: starts-after + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the start date of ``source`` with the end date of ``compare``. + +.. option:: ends-after + + :- source: The name of the ``Calends`` value to use. + :- compare: The ``Calends`` value to compare. + + Compares the end date of ``source`` with the end date of ``compare``. diff --git a/docs/installation.rst b/docs/installation.rst index 5f64a61..993a0f3 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -12,6 +12,7 @@ around the compiled shared library. Select your language to take a deeper look. .. toctree:: :maxdepth: 2 + Command Line Golang C/C++ PHP diff --git a/docs/php/installation.rst b/docs/php/installation.rst index 069c30f..c8d7dd6 100644 --- a/docs/php/installation.rst +++ b/docs/php/installation.rst @@ -36,6 +36,7 @@ Once those are installed, clone the repository, build ``libcalends``, then run cd $GOPATH/src/github.com/danhunsaker git clone https://github.com/danhunsaker/calends cd calends/libcalends + go get ../... go build -v -i -buildmode=c-shared -o libcalends.so cd php zephir install diff --git a/docs/usage.rst b/docs/usage.rst index 3f575b7..1b9c84e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -14,6 +14,7 @@ bit further to see how to do everything you need! .. toctree:: :maxdepth: 2 + Command Line Golang C/C++ PHP