<commit_before>/*

Package toggl provides an API for interacting with the Toggl time tracking service.

See https://github.com/toggl/toggl_api_docs for more information on Toggl's REST API.

*/
package toggl

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

// Toggl service constants
const (
	TogglAPI       = "https://toggl.com/api/v8"
	ReportsAPI     = "https://toggl.com/reports/api/v2"
	DefaultAppName = "go-toggl"
)

var (
	dlog   = log.New(os.Stderr, "[toggl] ", log.LstdFlags)
	client = &http.Client{}

	// AppName is the application name used when creating timers.
	AppName = DefaultAppName
)

// structures ///////////////////////////

// Session represents an active connection to the Toggl REST API.
type Session struct {
	APIToken string
	username string
	password string
}

// Account represents a user account.
type Account struct {
	Data struct {
		APIToken        string      `json:"api_token"`
		Timezone        string      `json:"timezone"`
		ID              int         `json:"id"`
		Workspaces      []Workspace `json:"workspaces"`
		Clients         []Client    `json:"clients"`
		Projects        []Project   `json:"projects"`
		Tasks           []Task      `json:"tasks"`
		Tags            []Tag       `json:"tags"`
		TimeEntries     []TimeEntry `json:"time_entries"`
		BeginningOfWeek int         `json:"beginning_of_week"`
	} `json:"data"`
	Since int `json:"since"`
}

// Workspace represents a user workspace.
type Workspace struct {
	ID              int    `json:"id"`
	RoundingMinutes int    `json:"rounding_minutes"`
	Rounding        int    `json:"rounding"`
	Name            string `json:"name"`
	Premium         bool   `json:"premium"`
}

// Client represents a client.
type Client struct {
	Wid   int    `json:"wid"`
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Notes string `json:"notes"`
}

// Project represents a project.
type Project struct {
	Wid             int        `json:"wid"`
	ID              int        `json:"id"`
	Cid             int        `json:"cid"`
	Name            string     `json:"name"`
	Active          bool       `json:"active"`
	Billable        bool       `json:"billable"`
	ServerDeletedAt *time.Time `json:"server_deleted_at,omitempty"`
}

// IsActive indicates whether a project exists and is active
func (p *Project) IsActive() bool {
	return p.Active && p.ServerDeletedAt == nil
}

// Task represents a task.
type Task struct {
	Wid  int    `json:"wid"`
	Pid  int    `json:"pid"`
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// Tag represents a tag.
type Tag struct {
	Wid  int    `json:"wid"`
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// TimeEntry represents a single time entry.
type TimeEntry struct {
	Wid         int        `json:"wid,omitempty"`
	ID          int        `json:"id,omitempty"`
	Pid         int        `json:"pid"`
	Tid         int        `json:"tid"`
	Description string     `json:"description,omitempty"`
	Stop        *time.Time `json:"stop,omitempty"`
	Start       *time.Time `json:"start,omitempty"`
	Tags        []string   `json:"tags"`
	Duration    int64      `json:"duration,omitempty"`
	DurOnly     bool       `json:"duronly"`
	Billable    bool       `json:"billable"`
}

type DetailedTimeEntry struct {
	ID              int        `json:"id"`
	Pid             int        `json:"pid"`
	Tid             int        `json:"tid"`
	Uid             int        `json:"uid"`
	User            string     `json:"user,omitempty"`
	Description     string     `json:"description"`
	Project         string     `json:"project"`
	ProjectColor    string     `json:"project_color"`
	ProjectHexColor string     `json:"project_hex_color"`
	Client          string     `json:"client"`
	Start           *time.Time `json:"start"`
	End             *time.Time `json:"end"`
	Updated         *time.Time `json:"updated"`
	Duration        int64      `json:"dur"`
	Billable        bool       `json:"billable"`
	Tags            []string   `json:"tags"`
}

// SummaryReport represents a summary report generated by Toggl's reporting API.
type SummaryReport struct {
	TotalGrand int `json:"total_grand"`
	Data       []struct {
		ID    int `json:"id"`
		Time  int `json:"time"`
		Title struct {
			Project string `json:"project"`
			Client  string `json:"client"`
		} `json:"title"`
		Items []struct {
			Title map[string]string `json:"title"`
			Time  int               `json:"time"`
		} `json:"items"`
	} `json:"data"`
}

// DetailedReport represents a summary report generated by Toggl's reporting API.
type DetailedReport struct {
	TotalGrand int `json:"total_grand"`
	TotalCount int `json:"total_count"`
	PerPage    int `json:"per_page"`
	Data []DetailedTimeEntry `json:"data"`
}

// functions ////////////////////////////

// OpenSession opens a session using an existing API token.
func OpenSession(apiToken string) Session {
	return Session{APIToken: apiToken}
}

// NewSession creates a new session by retrieving a user's API token.
func NewSession(username, password string) (session Session, err error) {
	session.username = username
	session.password = password

	data, err := session.get(TogglAPI, "/me", nil)
	if err != nil {
		return session, err
	}

	var account Account
	err = decodeAccount(data, &account)
	if err != nil {
		return session, err
	}

	session.username = ""
	session.password = ""
	session.APIToken = account.Data.APIToken

	return session, nil
}

// GetAccount returns a user's account information, including a list of active
// projects and timers.
func (session *Session) GetAccount() (Account, error) {
	params := map[string]string{"with_related_data": "true"}
	data, err := session.get(TogglAPI, "/me", params)
	if err != nil {
		return Account{}, err
	}

	var account Account
	err = decodeAccount(data, &account)
	return account, err
}

// GetSummaryReport retrieves a summary report using Toggle's reporting API.
func (session *Session) GetSummaryReport(workspace int, since, until string) (SummaryReport, error) {
	params := map[string]string{
		"user_agent":   "jc-toggl",
		"grouping":     "projects",
		"since":        since,
		"until":        until,
		"rounding":     "on",
		"workspace_id": fmt.Sprintf("%d", workspace)}
	data, err := session.get(ReportsAPI, "/summary", params)
	if err != nil {
		return SummaryReport{}, err
	}
	dlog.Printf("Got data: %s", data)

	var report SummaryReport
	err = decodeSummaryReport(data, &report)
	return report, err
}

// GetDetailedReport retrieves a detailed report using Toggle's reporting API.
func (session *Session) GetDetailedReport(workspace int, since, until string, page int) (DetailedReport, error) {
	params := map[string]string{
		"user_agent":   "jc-toggl",
		"since":        since,
		"until":        until,
		"page":			fmt.Sprintf("%d", page),
		"rounding":     "on",
		"workspace_id": fmt.Sprintf("%d", workspace)}
	data, err := session.get(ReportsAPI, "/details", params)
	if err != nil {
		return DetailedReport{}, err
	}
	dlog.Printf("Got data: %s", data)

	var report DetailedReport
	err = decodeDetailedReport(data, &report)
	return report, err
}

// StartTimeEntry creates a new time entry.
func (session *Session) StartTimeEntry(description string) (TimeEntry, error) {<commit_msg>Add Color, HexColor to SummaryReport<commit_after>/*

Package toggl provides an API for interacting with the Toggl time tracking service.

See https://github.com/toggl/toggl_api_docs for more information on Toggl's REST API.

*/
package toggl

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"time"
)

// Toggl service constants
const (
	TogglAPI       = "https://toggl.com/api/v8"
	ReportsAPI     = "https://toggl.com/reports/api/v2"
	DefaultAppName = "go-toggl"
)

var (
	dlog   = log.New(os.Stderr, "[toggl] ", log.LstdFlags)
	client = &http.Client{}

	// AppName is the application name used when creating timers.
	AppName = DefaultAppName
)

// structures ///////////////////////////

// Session represents an active connection to the Toggl REST API.
type Session struct {
	APIToken string
	username string
	password string
}

// Account represents a user account.
type Account struct {
	Data struct {
		APIToken        string      `json:"api_token"`
		Timezone        string      `json:"timezone"`
		ID              int         `json:"id"`
		Workspaces      []Workspace `json:"workspaces"`
		Clients         []Client    `json:"clients"`
		Projects        []Project   `json:"projects"`
		Tasks           []Task      `json:"tasks"`
		Tags            []Tag       `json:"tags"`
		TimeEntries     []TimeEntry `json:"time_entries"`
		BeginningOfWeek int         `json:"beginning_of_week"`
	} `json:"data"`
	Since int `json:"since"`
}

// Workspace represents a user workspace.
type Workspace struct {
	ID              int    `json:"id"`
	RoundingMinutes int    `json:"rounding_minutes"`
	Rounding        int    `json:"rounding"`
	Name            string `json:"name"`
	Premium         bool   `json:"premium"`
}

// Client represents a client.
type Client struct {
	Wid   int    `json:"wid"`
	ID    int    `json:"id"`
	Name  string `json:"name"`
	Notes string `json:"notes"`
}

// Project represents a project.
type Project struct {
	Wid             int        `json:"wid"`
	ID              int        `json:"id"`
	Cid             int        `json:"cid"`
	Name            string     `json:"name"`
	Active          bool       `json:"active"`
	Billable        bool       `json:"billable"`
	ServerDeletedAt *time.Time `json:"server_deleted_at,omitempty"`
}

// IsActive indicates whether a project exists and is active
func (p *Project) IsActive() bool {
	return p.Active && p.ServerDeletedAt == nil
}

// Task represents a task.
type Task struct {
	Wid  int    `json:"wid"`
	Pid  int    `json:"pid"`
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// Tag represents a tag.
type Tag struct {
	Wid  int    `json:"wid"`
	ID   int    `json:"id"`
	Name string `json:"name"`
}

// TimeEntry represents a single time entry.
type TimeEntry struct {
	Wid         int        `json:"wid,omitempty"`
	ID          int        `json:"id,omitempty"`
	Pid         int        `json:"pid"`
	Tid         int        `json:"tid"`
	Description string     `json:"description,omitempty"`
	Stop        *time.Time `json:"stop,omitempty"`
	Start       *time.Time `json:"start,omitempty"`
	Tags        []string   `json:"tags"`
	Duration    int64      `json:"duration,omitempty"`
	DurOnly     bool       `json:"duronly"`
	Billable    bool       `json:"billable"`
}

type DetailedTimeEntry struct {
	ID              int        `json:"id"`
	Pid             int        `json:"pid"`
	Tid             int        `json:"tid"`
	Uid             int        `json:"uid"`
	User            string     `json:"user,omitempty"`
	Description     string     `json:"description"`
	Project         string     `json:"project"`
	ProjectColor    string     `json:"project_color"`
	ProjectHexColor string     `json:"project_hex_color"`
	Client          string     `json:"client"`
	Start           *time.Time `json:"start"`
	End             *time.Time `json:"end"`
	Updated         *time.Time `json:"updated"`
	Duration        int64      `json:"dur"`
	Billable        bool       `json:"billable"`
	Tags            []string   `json:"tags"`
}

// SummaryReport represents a summary report generated by Toggl's reporting API.
type SummaryReport struct {
	TotalGrand int `json:"total_grand"`
	Data       []struct {
		ID    int `json:"id"`
		Time  int `json:"time"`
		Title struct {
			Project  string `json:"project"`
			Client   string `json:"client"`
			Color    string `json:"color"`
			HexColor string `json:"hex_color"`
		} `json:"title"`
		Items []struct {
			Title map[string]string `json:"title"`
			Time  int               `json:"time"`
		} `json:"items"`
	} `json:"data"`
}

// DetailedReport represents a summary report generated by Toggl's reporting API.
type DetailedReport struct {
	TotalGrand int                 `json:"total_grand"`
	TotalCount int                 `json:"total_count"`
	PerPage    int                 `json:"per_page"`
	Data       []DetailedTimeEntry `json:"data"`
}

// functions ////////////////////////////

// OpenSession opens a session using an existing API token.
func OpenSession(apiToken string) Session {
	return Session{APIToken: apiToken}
}

// NewSession creates a new session by retrieving a user's API token.
func NewSession(username, password string) (session Session, err error) {
	session.username = username
	session.password = password

	data, err := session.get(TogglAPI, "/me", nil)
	if err != nil {
		return session, err
	}

	var account Account
	err = decodeAccount(data, &account)
	if err != nil {
		return session, err
	}

	session.username = ""
	session.password = ""
	session.APIToken = account.Data.APIToken

	return session, nil
}

// GetAccount returns a user's account information, including a list of active
// projects and timers.
func (session *Session) GetAccount() (Account, error) {
	params := map[string]string{"with_related_data": "true"}
	data, err := session.get(TogglAPI, "/me", params)
	if err != nil {
		return Account{}, err
	}

	var account Account
	err = decodeAccount(data, &account)
	return account, err
}

// GetSummaryReport retrieves a summary report using Toggle's reporting API.
func (session *Session) GetSummaryReport(workspace int, since, until string) (SummaryReport, error) {
	params := map[string]string{
		"user_agent":   "jc-toggl",
		"grouping":     "projects",
		"since":        since,
		"until":        until,
		"rounding":     "on",
		"workspace_id": fmt.Sprintf("%d", workspace)}
	data, err := session.get(ReportsAPI, "/summary", params)
	if err != nil {
		return SummaryReport{}, err
	}
	dlog.Printf("Got data: %s", data)

	var report SummaryReport
	err = decodeSummaryReport(data, &report)
	return report, err
}

// GetDetailedReport retrieves a detailed report using Toggle's reporting API.
func (session *Session) GetDetailedReport(workspace int, since, until string, page int) (DetailedReport, error) {
	params := map[string]string{
		"user_agent":   "jc-toggl",
		"since":        since,
		"until":        until,
		"page":         fmt.Sprintf("%d", page),
		"rounding":     "on",
		"workspace_id": fmt.Sprintf("%d", workspace)}
	data, err := session.get(ReportsAPI, "/details", params)
	if err != nil {
		return DetailedReport{}, err
	}
	dlog.Printf("Got data: %s", data)

	var report DetailedReport
	err = decodeDetailedReport(data, &report)
	return report, err
}

// StartTimeEntry creates a new time entry.
func (session *Session) StartTimeEntry(description string) (TimeEntry, error) {
{'fraction_non_alphanumeric': 0.13328318402516584, 'fraction_numerical': 0.0008206250427408876, 'mean_word_length': 2.804370447450572, 'pattern_counts': {'":': 26, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4161246', 'n_tokens_mistral': 4672, 'n_tokens_neox': 4515, 'n_words': 1324}