<commit_before>package bundled

const epmElv = `use re

# Verbosity configuration
debug-mode = $false

# Configuration for common domains
-default-domain-config = [
  &"github.com"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
  &"bitbucket.org"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
  &"gitlab.com"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
]

# Internal configuration
-data-dir = ~/.elvish
-lib-dir = $-data-dir/lib

# General utility functions

fn -debug [text]{
  if $debug-mode {
    print (styled '=> ' blue)
    echo $text
  }
}

fn -info [text]{
  print (styled '=> ' green)
  echo $text
}

fn -warn [text]{
  print (styled '=> ' yellow)
  echo $text
}

fn -error [text]{
  print (styled '=> ' red)
  echo $text
}

fn dest [pkg]{
  put $-lib-dir/$pkg
}

fn is-installed [pkg]{
  bool ?(test -e (dest $pkg))
}

fn -package-domain [pkg]{
  splits &max=2 / $pkg | take 1
}

fn -package-without-domain [pkg]{
  splits &max=2 / $pkg | drop 1 | joins ''
}

# Merge two maps
fn -merge [a b]{
  keys $b | each [k]{ a[$k] = $b[$k] }
  put $a
}

# Uppercase first letter of a string
fn -first-upper [s]{
  put (echo $s[0] | tr '[:lower:]' '[:upper:]')$s[(count $s[0]):]
}

# Expand tilde at the beginning of a string to the home dir
fn -tilde-expand [p]{
  re:replace "^~" $E:HOME $p
}

# Known method handlers. Each entry is indexed by method name (the
# value of the "method" key in the domain configs), and must contain
# two keys: install and upgrade, each one must be a closure that
# received two arguments: package name and the domain config entry
#
# - Method 'git' requires the key 'protocol' in the domain config,
#   which has to be 'http' or 'https'
# - Method 'rsync' requires the key 'location' in the domain config,
#   which has to contain the directory where the domain files are
#   stored. It can be any source location understood by the rsync
#   command.
-method-handler = [
  &git= [
    &src= [pkg dom-cfg]{
      put $dom-cfg[protocol]"://"$pkg
    }

    &install= [pkg dom-cfg]{
      dest = (dest $pkg)
      -info "Installing "$pkg
      mkdir -p $dest
      git clone ($-method-handler[git][src] $pkg $dom-cfg) $dest
    }

    &upgrade= [pkg dom-cfg]{
      dest = (dest $pkg)
      -info "Updating "$pkg
      try {
        git -C $dest pull
      } except _ {
          -error "Something failed, please check error above and retry."
      }
    }
  ]

  &rsync= [
    &src= [pkg dom-cfg]{
      put (-tilde-expand $dom-cfg[location])/(-package-without-domain $pkg)/
    }

    &install= [pkg dom-cfg]{
      dest = (dest $pkg)
      pkgd = (-package-without-domain $pkg)
      -info "Installing "$pkg
      rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
    }

    &upgrade= [pkg dom-cfg]{
      dest = (dest $pkg)
      pkgd = (-package-without-domain $pkg)
      if (not (is-installed $pkg)) {
        -error "Package "$pkg" is not installed."
        return
      }
      -info "Updating "$pkg
      rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
    }
  ]
]

# Return the filename of the domain config file for the given domain
# (regardless of whether it exists)
fn -domain-config-file [dom]{
  put $-lib-dir/$dom/epm-domain.cfg
}

# Return the filename of the metadata file for the given package
# (regardless of whether it exists)
fn -package-metadata-file [pkg]{
  put (dest $pkg)/metadata.json
}

fn -write-domain-config [dom]{
  cfgfile = (-domain-config-file $dom)
  mkdir -p (dirname $cfgfile)
  if (has-key $-default-domain-config $dom) {
    put $-default-domain-config[$dom] | to-json > $cfgfile
  } else {
    -error "No default config exists for domain "$dom"."
  }
}

# Returns the domain config for a given domain, as a JSON data
# structure. If the file does not exist but we have a built-in
# definition, then we return the default. Otherwise we return $false,
# so the result can always be checked with 'if'.
fn -domain-config [dom]{
  cfgfile = (-domain-config-file $dom)
  cfg = $false
  if ?(test -f $cfgfile) {
    # If the config file exists, read it...
    cfg = (cat $cfgfile | from-json)
    -debug "Read domain config for "$dom": "(to-string $cfg)
  } else {
    # ...otherwise check if we have a default config for the domain, and save it
    if (has-key $-default-domain-config $dom) {
      cfg = $-default-domain-config[$dom]
      -debug "No existing config for "$dom", using the default: "(to-string $cfg)
    } else {
      -debug "No existing config for "$dom" and no default available."
    }
  }
  put $cfg
}

# Return the method by which a package is installed
fn -package-method [pkg]{
  dom = (-package-domain $pkg)
  cfg = (-domain-config $dom)
  if $cfg {
    put $cfg[method]
  } else {
    put $false
  }
}

# Invoke package operations defined in $-method-handler above
fn -package-op [pkg what]{
  dom = (-package-domain $pkg)
  cfg = (-domain-config $dom)
  if $cfg {
    method = $cfg[method]
    if (has-key $-method-handler $method) {
      if (has-key $-method-handler[$method] $what) {
        $-method-handler[$method][$what] $pkg $cfg
      } else {
        fail "Unknown operation '"$what"' for package "$pkg
      }
    } else {
      fail "Unknown method '"$method"', specified in in config file "(-domain-config-file $dom)
    }
  } else {
    -error "No config for domain '"$dom"'."
  }
}

# Uninstall a single package by removing its directory
fn -uninstall-package [pkg]{
  if (not (is-installed $pkg)) {
    -error "Package "$pkg" is not installed."
    return
  }
  dest = (dest $pkg)
  -info "Removing package "$pkg
  rm -rf $dest
}

######################################################################
# Main user-facing functions

# Read and parse the package metadata, if it exists
fn metadata [pkg]{
  # Base metadata attributes
  res = [
    &name= $pkg
    &method= (-package-method $pkg)
    &src= (-package-op $pkg src)
    &dst= (dest $pkg)
    &installed= (is-installed $pkg)
  ]
  # Merge with package-specified attributes, if any
  file = (-package-metadata-file $pkg)
  if (and (is-installed $pkg) ?(test -f $file)) {
    res = (-merge (cat $file | from-json) $res)
  }
  put $res
}

# Print out information about a package
fn query [pkg]{
  data = (metadata $pkg)
  special-keys = [name method installed src dst]
  echo (styled "Package "$data[name] cyan)
  if $data[installed] {
    echo (styled "Installed at "$data[dst] green)
  } else {
    echo (styled "Not installed" red)
  }
  echo (styled "Source:" blue) $data[method] $data[src]
  keys $data | each [key]{
    if (not (has-value $special-keys $key)) {
      val = $data[$key]
      if (eq (kind-of $val) list) {
        val = (joins ", " $val)
      }
      echo (styled (-first-upper $key)":" blue) $val
    }
  }
}

# List installed packages
fn installed {
  put $-lib-dir/*[nomatch-ok] | each [dir]{
    dom = (re:replace &literal $-lib-dir/ '' $dir)
    cfg = (-domain-config $dom)
    # Only list domains for which we know the config, so that the user
    # can have his own non-package directories under ~/.elvish/lib
    # without conflicts.
    if $cfg {
      lvl = $cfg[levels]
      pat = '^\Q'$-lib-dir'/\E('(repeat (+ $lvl 1) '[^/]+' | joins '/')')/$'
      put (each [d]{ re:find $pat $d } [ $-lib-dir/$dom/**[nomatch-ok]/ ] )[groups][1][text]
    }
  }
}

# epm:list is an alias for epm:installed
fn list { installed }

# Install and upgrade are method-specific, so we call the
# corresponding functions using -package-op
fn install [&silent-if-installed=$false @pkgs]{
  if (eq $pkgs []) {
    -error "You must specify at least one package."
    return
  }
  for pkg $pkgs {
    if (is-installed $pkg) {
      if (not $silent-if-installed) {
        -info "Package "$pkg" is already installed."
      }
    } else {
      -package-op $pkg install
      # Check if there are any dependencies to install
      metadata = (metadata $pkg)
      if (has-key $metadata dependencies) {
        deps = $metadata[dependencies]
        -info "Installing dependencies: "(joins " " $deps)
        # If the installation of dependencies fails, uninstall the
        # target package (leave any already-installed dependencies in
        # place)
        try {
          install $@deps
        } except e {
          -error "Dependency installation failed. Uninstalling "$pkg", please check the errors above and try again."
          -uninstall-package $pkg
        }
      }
    }
  }
}

fn upgrade [@pkgs]{
  if (eq $pkgs []) {
    pkgs = [(installed)]
    -info 'Upgrading all installed packages'
  }
  for pkg $pkgs {
    if (not (is-installed $pkg)) {
      -error "Package "$pkg" is not installed."
    } else {
      -package-op $pkg upgrade
    }
  }
}

# Uninstall is the same for everyone, just remove the directory
fn uninstall [@pkgs]{
  if (eq $pkgs []) {
    -error 'You must specify at least one package.'
    return
  }
  for pkg $pkgs {
    -uninstall-package $pkg
  }
}`
<commit_msg>Replace re:replace with replaces in epm (#756)<commit_after>package bundled

const epmElv = `use re

# Verbosity configuration
debug-mode = $false

# Configuration for common domains
-default-domain-config = [
  &"github.com"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
  &"bitbucket.org"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
  &"gitlab.com"= [
    &method= git
    &protocol= https
    &levels= 2
  ]
]

# Internal configuration
-data-dir = ~/.elvish
-lib-dir = $-data-dir/lib

# General utility functions

fn -debug [text]{
  if $debug-mode {
    print (styled '=> ' blue)
    echo $text
  }
}

fn -info [text]{
  print (styled '=> ' green)
  echo $text
}

fn -warn [text]{
  print (styled '=> ' yellow)
  echo $text
}

fn -error [text]{
  print (styled '=> ' red)
  echo $text
}

fn dest [pkg]{
  put $-lib-dir/$pkg
}

fn is-installed [pkg]{
  bool ?(test -e (dest $pkg))
}

fn -package-domain [pkg]{
  splits &max=2 / $pkg | take 1
}

fn -package-without-domain [pkg]{
  splits &max=2 / $pkg | drop 1 | joins ''
}

# Merge two maps
fn -merge [a b]{
  keys $b | each [k]{ a[$k] = $b[$k] }
  put $a
}

# Uppercase first letter of a string
fn -first-upper [s]{
  put (echo $s[0] | tr '[:lower:]' '[:upper:]')$s[(count $s[0]):]
}

# Expand tilde at the beginning of a string to the home dir
fn -tilde-expand [p]{
  re:replace "^~" $E:HOME $p
}

# Known method handlers. Each entry is indexed by method name (the
# value of the "method" key in the domain configs), and must contain
# two keys: install and upgrade, each one must be a closure that
# received two arguments: package name and the domain config entry
#
# - Method 'git' requires the key 'protocol' in the domain config,
#   which has to be 'http' or 'https'
# - Method 'rsync' requires the key 'location' in the domain config,
#   which has to contain the directory where the domain files are
#   stored. It can be any source location understood by the rsync
#   command.
-method-handler = [
  &git= [
    &src= [pkg dom-cfg]{
      put $dom-cfg[protocol]"://"$pkg
    }

    &install= [pkg dom-cfg]{
      dest = (dest $pkg)
      -info "Installing "$pkg
      mkdir -p $dest
      git clone ($-method-handler[git][src] $pkg $dom-cfg) $dest
    }

    &upgrade= [pkg dom-cfg]{
      dest = (dest $pkg)
      -info "Updating "$pkg
      try {
        git -C $dest pull
      } except _ {
          -error "Something failed, please check error above and retry."
      }
    }
  ]

  &rsync= [
    &src= [pkg dom-cfg]{
      put (-tilde-expand $dom-cfg[location])/(-package-without-domain $pkg)/
    }

    &install= [pkg dom-cfg]{
      dest = (dest $pkg)
      pkgd = (-package-without-domain $pkg)
      -info "Installing "$pkg
      rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
    }

    &upgrade= [pkg dom-cfg]{
      dest = (dest $pkg)
      pkgd = (-package-without-domain $pkg)
      if (not (is-installed $pkg)) {
        -error "Package "$pkg" is not installed."
        return
      }
      -info "Updating "$pkg
      rsync -av ($-method-handler[rsync][src] $pkg $dom-cfg) $dest
    }
  ]
]

# Return the filename of the domain config file for the given domain
# (regardless of whether it exists)
fn -domain-config-file [dom]{
  put $-lib-dir/$dom/epm-domain.cfg
}

# Return the filename of the metadata file for the given package
# (regardless of whether it exists)
fn -package-metadata-file [pkg]{
  put (dest $pkg)/metadata.json
}

fn -write-domain-config [dom]{
  cfgfile = (-domain-config-file $dom)
  mkdir -p (dirname $cfgfile)
  if (has-key $-default-domain-config $dom) {
    put $-default-domain-config[$dom] | to-json > $cfgfile
  } else {
    -error "No default config exists for domain "$dom"."
  }
}

# Returns the domain config for a given domain, as a JSON data
# structure. If the file does not exist but we have a built-in
# definition, then we return the default. Otherwise we return $false,
# so the result can always be checked with 'if'.
fn -domain-config [dom]{
  cfgfile = (-domain-config-file $dom)
  cfg = $false
  if ?(test -f $cfgfile) {
    # If the config file exists, read it...
    cfg = (cat $cfgfile | from-json)
    -debug "Read domain config for "$dom": "(to-string $cfg)
  } else {
    # ...otherwise check if we have a default config for the domain, and save it
    if (has-key $-default-domain-config $dom) {
      cfg = $-default-domain-config[$dom]
      -debug "No existing config for "$dom", using the default: "(to-string $cfg)
    } else {
      -debug "No existing config for "$dom" and no default available."
    }
  }
  put $cfg
}

# Return the method by which a package is installed
fn -package-method [pkg]{
  dom = (-package-domain $pkg)
  cfg = (-domain-config $dom)
  if $cfg {
    put $cfg[method]
  } else {
    put $false
  }
}

# Invoke package operations defined in $-method-handler above
fn -package-op [pkg what]{
  dom = (-package-domain $pkg)
  cfg = (-domain-config $dom)
  if $cfg {
    method = $cfg[method]
    if (has-key $-method-handler $method) {
      if (has-key $-method-handler[$method] $what) {
        $-method-handler[$method][$what] $pkg $cfg
      } else {
        fail "Unknown operation '"$what"' for package "$pkg
      }
    } else {
      fail "Unknown method '"$method"', specified in in config file "(-domain-config-file $dom)
    }
  } else {
    -error "No config for domain '"$dom"'."
  }
}

# Uninstall a single package by removing its directory
fn -uninstall-package [pkg]{
  if (not (is-installed $pkg)) {
    -error "Package "$pkg" is not installed."
    return
  }
  dest = (dest $pkg)
  -info "Removing package "$pkg
  rm -rf $dest
}

######################################################################
# Main user-facing functions

# Read and parse the package metadata, if it exists
fn metadata [pkg]{
  # Base metadata attributes
  res = [
    &name= $pkg
    &method= (-package-method $pkg)
    &src= (-package-op $pkg src)
    &dst= (dest $pkg)
    &installed= (is-installed $pkg)
  ]
  # Merge with package-specified attributes, if any
  file = (-package-metadata-file $pkg)
  if (and (is-installed $pkg) ?(test -f $file)) {
    res = (-merge (cat $file | from-json) $res)
  }
  put $res
}

# Print out information about a package
fn query [pkg]{
  data = (metadata $pkg)
  special-keys = [name method installed src dst]
  echo (styled "Package "$data[name] cyan)
  if $data[installed] {
    echo (styled "Installed at "$data[dst] green)
  } else {
    echo (styled "Not installed" red)
  }
  echo (styled "Source:" blue) $data[method] $data[src]
  keys $data | each [key]{
    if (not (has-value $special-keys $key)) {
      val = $data[$key]
      if (eq (kind-of $val) list) {
        val = (joins ", " $val)
      }
      echo (styled (-first-upper $key)":" blue) $val
    }
  }
}

# List installed packages
fn installed {
  put $-lib-dir/*[nomatch-ok] | each [dir]{
    dom = (replaces $-lib-dir/ '' $dir)
    cfg = (-domain-config $dom)
    # Only list domains for which we know the config, so that the user
    # can have his own non-package directories under ~/.elvish/lib
    # without conflicts.
    if $cfg {
      lvl = $cfg[levels]
      pat = '^\Q'$-lib-dir'/\E('(repeat (+ $lvl 1) '[^/]+' | joins '/')')/$'
      put (each [d]{ re:find $pat $d } [ $-lib-dir/$dom/**[nomatch-ok]/ ] )[groups][1][text]
    }
  }
}

# epm:list is an alias for epm:installed
fn list { installed }

# Install and upgrade are method-specific, so we call the
# corresponding functions using -package-op
fn install [&silent-if-installed=$false @pkgs]{
  if (eq $pkgs []) {
    -error "You must specify at least one package."
    return
  }
  for pkg $pkgs {
    if (is-installed $pkg) {
      if (not $silent-if-installed) {
        -info "Package "$pkg" is already installed."
      }
    } else {
      -package-op $pkg install
      # Check if there are any dependencies to install
      metadata = (metadata $pkg)
      if (has-key $metadata dependencies) {
        deps = $metadata[dependencies]
        -info "Installing dependencies: "(joins " " $deps)
        # If the installation of dependencies fails, uninstall the
        # target package (leave any already-installed dependencies in
        # place)
        try {
          install $@deps
        } except e {
          -error "Dependency installation failed. Uninstalling "$pkg", please check the errors above and try again."
          -uninstall-package $pkg
        }
      }
    }
  }
}

fn upgrade [@pkgs]{
  if (eq $pkgs []) {
    pkgs = [(installed)]
    -info 'Upgrading all installed packages'
  }
  for pkg $pkgs {
    if (not (is-installed $pkg)) {
      -error "Package "$pkg" is not installed."
    } else {
      -package-op $pkg upgrade
    }
  }
}

# Uninstall is the same for everyone, just remove the directory
fn uninstall [@pkgs]{
  if (eq $pkgs []) {
    -error 'You must specify at least one package.'
    return
  }
  for pkg $pkgs {
    -uninstall-package $pkg
  }
}`
<|endoftext|>


<commit_before>package survey

import (
	"database/sql"
	"errors"
	"github.com/curt-labs/GoSurvey/helpers/database"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

var (
	insertUser = `insert into SurveyUser(fname, lname, email)
								values(?,?,?)`
	deleteUser       = `delete from SurveyUser where id = ?`
	insertUserAnswer = `insert into SurveyUserAnswer(userID, surveyID, questionID, answer)
									values(?,?,?,?)`
	getAllSubmissions = `select su.id, su.fname, su.lname, su.email,
												sua.answer, sq.question, sua.date_answered
												from SurveyUserAnswer sua
												join SurveyUser as su on sua.userID = su.id
												join SurveyQuestion as sq on sua.questionID = sq.id
												order by su.date_added desc
												limit ?,?`
	getAllSubmissionsBySurvey = `select su.id, su.fname, su.lname, su.email,
												sua.answer, sq.question, sua.date_answered
												from SurveyUserAnswer sua
												join SurveyUser as su on sua.userID = su.id
												join SurveyQuestion as sq on sua.questionID = sq.id
												where sua.surveyID = ?
												order by su.date_added desc
												limit ?,?`
	getSubmissionCount         = `select count(id) as count from SurveyUser`
	getSubmissionCountBySurvey = `select count(distinct su.id) as count from SurveyUser as su
																join SurveyUserAnswer as sua on su.id = sua.userID
																where sua.surveyID = ?`
)

type SurveySubmission struct {
	ID        int                      `json:"id"`
	User      SurveyUser               `json:"user"`
	Questions []SurveySubmissionAnswer `json:"questions"`
}

type SurveyUser struct {
	ID        int       `json:"id"`
	FirstName string    `json:"fname"`
	LastName  string    `json:"lname"`
	Email     string    `json:"email"`
	DateAdded time.Time `json:"date_added"`
}

type SurveySubmissionAnswer struct {
	ID           int       `json:"id"`
	Answer       string    `json:"answer"`
	Question     string    `json:"question"`
	DateAnswered time.Time `json:"date_answered"`
}

func GetAllSubmissions(skip, take, surveyID int) ([]SurveySubmission, error) {
	if take == 0 {
		take = 25
	}

	if skip > 0 {
		skip = (skip - 1) * take
	}

	submissions := make([]SurveySubmission, 0)
	var err error

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return submissions, err
	}
	defer db.Close()

	var stmt *sql.Stmt
	if surveyID == 0 {
		stmt, err = db.Prepare(getAllSubmissions)
	} else {
		stmt, err = db.Prepare(getAllSubmissionsBySurvey)
	}

	if err != nil {
		return submissions, err
	}
	defer stmt.Close()

	var res *sql.Rows
	if surveyID == 0 {
		res, err = stmt.Query(skip, take)
	} else {
		res, err = stmt.Query(surveyID, skip, take)
	}
	if err != nil {
		return submissions, err
	}

	indexedSubmissions := make(map[int]SurveySubmission, 0)

	for res.Next() {
		var ans SurveySubmissionAnswer
		var user SurveyUser

		if err = res.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &ans.Answer, &ans.Question, &ans.DateAnswered); err == nil {
			if sm, _ := indexedSubmissions[user.ID]; sm.ID == 0 {
				indexedSubmissions[user.ID] = SurveySubmission{
					ID:   user.ID,
					User: user,
				}
			}

			sb := indexedSubmissions[user.ID]
			sb.Questions = append(sb.Questions, ans)
			indexedSubmissions[user.ID] = sb
		}
	}

	for _, sb := range indexedSubmissions {
		submissions = append(submissions, sb)
	}

	return submissions, nil
}

func SubmissionCount(surveyID int) int {
	var err error

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return 0
	}

	defer db.Close()

	var stmt *sql.Stmt
	if surveyID == 0 {
		stmt, err = db.Prepare(getSubmissionCount)
	} else {
		stmt, err = db.Prepare(getSubmissionCountBySurvey)
	}

	if err != nil {
		return 0
	}

	defer stmt.Close()

	var total int
	if surveyID == 0 {
		stmt.QueryRow().Scan(&total)
	} else {
		stmt.QueryRow(surveyID).Scan(&total)
	}

	return total
}

func (s *SurveySubmission) Submit() error {

	if len(s.Questions) == 0 {
		return errors.New("cannot submit a survey without answers")
	}

	surv := Survey{
		ID: s.ID,
	}

	if err := surv.Get(); err != nil {
		return err
	}

	if err := s.User.save(); err != nil {
		return err
	}

	ch := make(chan error)

	for _, question := range s.Questions {
		go func(ss *SurveySubmission, q SurveySubmissionAnswer) {
			ch <- q.save(ss.User.ID, ss.ID)
		}(s, question)
	}

	for _, _ = range s.Questions {
		if err := <-ch; err != nil {
			s.User.delete()
			return err
		}
	}

	return nil
}

func (s *SurveyUser) save() error {
	if s.FirstName == "" {
		return errors.New("first name cannot be blank")
	}
	if s.LastName == "" {
		return errors.New("last name cannot be blank")
	}
	if s.Email == "" {
		return errors.New("e-mail name cannot be blank")
	}

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(insertUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	res, err := stmt.Exec(s.FirstName, s.LastName, s.Email)
	if err != nil {
		return err
	}

	id, err := res.LastInsertId()
	if err != nil {
		return err
	}

	s.ID = int(id)

	return nil
}

func (s *SurveySubmissionAnswer) save(userID, surveyID int) error {
	if s.Answer == "" {
		return errors.New("answers cannot be blank")
	}
	if userID == 0 {
		return errors.New("failed to set name and/or email")
	}
	if s.ID == 0 {
		return errors.New("failed to assign question")
	}
	if surveyID == 0 {
		return errors.New("failed to assign survey")
	}

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(insertUserAnswer)
	if err != nil {
		return err
	}
	defer stmt.Close()

	_, err = stmt.Exec(userID, surveyID, s.ID, s.Answer)
	return err
}

func (s *SurveyUser) delete() error {
	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(deleteUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	_, err = stmt.Exec(s.ID)
	return err
}
<commit_msg>added survey object to submission request<commit_after>package survey

import (
	"database/sql"
	"errors"
	"github.com/curt-labs/GoSurvey/helpers/database"
	_ "github.com/go-sql-driver/mysql"
	"time"
)

var (
	insertUser = `insert into SurveyUser(fname, lname, email)
								values(?,?,?)`
	deleteUser       = `delete from SurveyUser where id = ?`
	insertUserAnswer = `insert into SurveyUserAnswer(userID, surveyID, questionID, answer)
									values(?,?,?,?)`
	getAllSubmissions = `select su.id, su.fname, su.lname, su.email,
												sua.answer, sq.question, sua.date_answered, sua.surveyID
												from SurveyUserAnswer sua
												join SurveyUser as su on sua.userID = su.id
												join SurveyQuestion as sq on sua.questionID = sq.id
												order by su.date_added desc
												limit ?,?`
	getAllSubmissionsBySurvey = `select su.id, su.fname, su.lname, su.email,
												sua.answer, sq.question, sua.date_answered, sua.surveyID
												from SurveyUserAnswer sua
												join SurveyUser as su on sua.userID = su.id
												join SurveyQuestion as sq on sua.questionID = sq.id
												where sua.surveyID = ?
												order by su.date_added desc
												limit ?,?`
	getSubmissionCount         = `select count(id) as count from SurveyUser`
	getSubmissionCountBySurvey = `select count(distinct su.id) as count from SurveyUser as su
																join SurveyUserAnswer as sua on su.id = sua.userID
																where sua.surveyID = ?`
)

type SurveySubmission struct {
	ID        int                      `json:"id"`
	User      SurveyUser               `json:"user"`
	Questions []SurveySubmissionAnswer `json:"questions"`
	Survey    Survey                   `json:"survey"`
}

type SurveyUser struct {
	ID        int       `json:"id"`
	FirstName string    `json:"fname"`
	LastName  string    `json:"lname"`
	Email     string    `json:"email"`
	DateAdded time.Time `json:"date_added"`
}

type SurveySubmissionAnswer struct {
	ID           int       `json:"id"`
	Answer       string    `json:"answer"`
	Question     string    `json:"question"`
	DateAnswered time.Time `json:"date_answered"`
}

func GetAllSubmissions(skip, take, surveyID int) ([]SurveySubmission, error) {
	if take == 0 {
		take = 25
	}

	if skip > 0 {
		skip = (skip - 1) * take
	}

	submissions := make([]SurveySubmission, 0)
	var err error

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return submissions, err
	}
	defer db.Close()

	var stmt *sql.Stmt
	if surveyID == 0 {
		stmt, err = db.Prepare(getAllSubmissions)
	} else {
		stmt, err = db.Prepare(getAllSubmissionsBySurvey)
	}

	if err != nil {
		return submissions, err
	}
	defer stmt.Close()

	var res *sql.Rows
	if surveyID == 0 {
		res, err = stmt.Query(skip, take)
	} else {
		res, err = stmt.Query(surveyID, skip, take)
	}
	if err != nil {
		return submissions, err
	}

	indexedSubmissions := make(map[int]SurveySubmission, 0)

	for res.Next() {
		var ans SurveySubmissionAnswer
		var user SurveyUser
		var s Survey

		if err = res.Scan(&user.ID, &user.FirstName, &user.LastName, &user.Email, &ans.Answer, &ans.Question, &ans.DateAnswered, &s.ID); err == nil {
			if sm, _ := indexedSubmissions[user.ID]; sm.ID == 0 {
				ss := SurveySubmission{
					ID:   user.ID,
					User: user,
				}

				if err := s.Get(); err == nil {
					ss.Survey = s
				}
				indexedSubmissions[user.ID] = ss
			}

			sb := indexedSubmissions[user.ID]
			sb.Questions = append(sb.Questions, ans)
			indexedSubmissions[user.ID] = sb
		}
	}

	for _, sb := range indexedSubmissions {
		submissions = append(submissions, sb)
	}

	return submissions, nil
}

func SubmissionCount(surveyID int) int {
	var err error

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return 0
	}

	defer db.Close()

	var stmt *sql.Stmt
	if surveyID == 0 {
		stmt, err = db.Prepare(getSubmissionCount)
	} else {
		stmt, err = db.Prepare(getSubmissionCountBySurvey)
	}

	if err != nil {
		return 0
	}

	defer stmt.Close()

	var total int
	if surveyID == 0 {
		stmt.QueryRow().Scan(&total)
	} else {
		stmt.QueryRow(surveyID).Scan(&total)
	}

	return total
}

func (s *SurveySubmission) Submit() error {

	if len(s.Questions) == 0 {
		return errors.New("cannot submit a survey without answers")
	}

	surv := Survey{
		ID: s.ID,
	}

	if err := surv.Get(); err != nil {
		return err
	}

	if err := s.User.save(); err != nil {
		return err
	}

	ch := make(chan error)

	for _, question := range s.Questions {
		go func(ss *SurveySubmission, q SurveySubmissionAnswer) {
			ch <- q.save(ss.User.ID, ss.ID)
		}(s, question)
	}

	for _, _ = range s.Questions {
		if err := <-ch; err != nil {
			s.User.delete()
			return err
		}
	}

	return nil
}

func (s *SurveyUser) save() error {
	if s.FirstName == "" {
		return errors.New("first name cannot be blank")
	}
	if s.LastName == "" {
		return errors.New("last name cannot be blank")
	}
	if s.Email == "" {
		return errors.New("e-mail name cannot be blank")
	}

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(insertUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	res, err := stmt.Exec(s.FirstName, s.LastName, s.Email)
	if err != nil {
		return err
	}

	id, err := res.LastInsertId()
	if err != nil {
		return err
	}

	s.ID = int(id)

	return nil
}

func (s *SurveySubmissionAnswer) save(userID, surveyID int) error {
	if s.Answer == "" {
		return errors.New("answers cannot be blank")
	}
	if userID == 0 {
		return errors.New("failed to set name and/or email")
	}
	if s.ID == 0 {
		return errors.New("failed to assign question")
	}
	if surveyID == 0 {
		return errors.New("failed to assign survey")
	}

	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(insertUserAnswer)
	if err != nil {
		return err
	}
	defer stmt.Close()

	_, err = stmt.Exec(userID, surveyID, s.ID, s.Answer)
	return err
}

func (s *SurveyUser) delete() error {
	db, err := sql.Open("mysql", database.ConnectionString())
	if err != nil {
		return err
	}
	defer db.Close()

	stmt, err := db.Prepare(deleteUser)
	if err != nil {
		return err
	}
	defer stmt.Close()

	_, err = stmt.Exec(s.ID)
	return err
}
<|endoftext|>


<commit_before>package scope

import (
	"fmt"

	"github.com/anchore/imgbom/imgbom/scope/resolvers"
	"github.com/anchore/stereoscope/pkg/file"
	"github.com/anchore/stereoscope/pkg/image"
)

type FileContentResolver interface {
	ContentResolver
	FileResolver
}

type ContentResolver interface {
	MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error)
}

type FileResolver interface {
	FilesByPath(paths ...file.Path) ([]file.Reference, error)
	FilesByGlob(patterns ...string) ([]file.Reference, error)
}

func getFileResolver(img *image.Image, option Option) (FileResolver, error) {
	switch option {
	case SquashedScope:
		return resolvers.NewImageSquashResolver(img)
	case AllLayersScope:
		return resolvers.NewAllLayersResolver(img)
	default:
		return nil, fmt.Errorf("bad option provided: %+v", option)
	}
}
<commit_msg>scope: create a new generic Resolver interface<commit_after>package scope

import (
	"fmt"

	"github.com/anchore/imgbom/imgbom/scope/resolvers"
	"github.com/anchore/stereoscope/pkg/file"
	"github.com/anchore/stereoscope/pkg/image"
)

type Resolver interface {
	ContentResolver // knows how to get content from file.References
	FileResolver    // knows how to get file.References from string paths and globs
}

type ContentResolver interface {
	MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error)
}

type FileResolver interface {
	FilesByPath(paths ...file.Path) ([]file.Reference, error)
	FilesByGlob(patterns ...string) ([]file.Reference, error)
}

func getImageResolver(img *image.Image, option Option) (Resolver, error) {
	switch option {
	case SquashedScope:
		return resolvers.NewImageSquashResolver(img)
	case AllLayersScope:
		return resolvers.NewAllLayersResolver(img)
	default:
		return nil, fmt.Errorf("bad option provided: %+v", option)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build java android

package discovery

import (
	"bytes"
	"encoding/binary"

	"v.io/v23/context"
	"v.io/v23/discovery"

	idiscovery "v.io/x/ref/lib/discovery"
	fdiscovery "v.io/x/ref/lib/discovery/factory"
	"v.io/x/ref/lib/discovery/plugins/mock"

	jutil "v.io/x/jni/util"
)

// JavaDiscovery converts a Go discovery instance into a Java discovery instance.
func JavaDiscovery(env jutil.Env, d discovery.T) (jutil.Object, error) {
	ref := jutil.GoNewRef(&d) // Un-refed when jDiscovery is finalized.
	jDiscovery, err := jutil.NewObject(env, jDiscoveryImplClass, []jutil.Sign{jutil.LongSign}, int64(ref))
	if err != nil {
		jutil.GoDecRef(ref)
		return jutil.NullObject, err
	}
	return jDiscovery, nil
}

// javaUpdate converts a Go update instance into a Java update instance.
func javaUpdate(env jutil.Env, update discovery.Update) (jutil.Object, error) {
	jAd, err := jutil.JVomCopy(env, update.Advertisement(), jAdvertisementClass)
	if err != nil {
		return jutil.NullObject, err
	}
	ref := jutil.GoNewRef(&update) // Un-refed when jUpdate is finalized.
	jUpdate, err := jutil.NewObject(env, jUpdateImplClass, []jutil.Sign{jutil.LongSign, jutil.BoolSign, advertisementSign}, int64(ref), update.IsLost(), jAd)
	if err != nil {
		jutil.GoDecRef(ref)
		return jutil.NullObject, err
	}
	return jUpdate, nil
}

// javaUUID converts a Go UUID into a Java UUID instance.
func javaUUID(env jutil.Env, uuid idiscovery.Uuid) (jutil.Object, error) {
	var high, low int64
	buf := bytes.NewReader(uuid)
	binary.Read(buf, binary.BigEndian, &high)
	binary.Read(buf, binary.BigEndian, &low)
	return jutil.NewObject(env, jUUIDClass, []jutil.Sign{jutil.LongSign, jutil.LongSign}, high, low)
}

// injectMockPlugin injects a discovery factory with a mock plugin into a runtime.
func injectMockPlugin(ctx *context.T) error {
	df, err := idiscovery.NewFactory(ctx, mock.New())
	if err != nil {
		return err
	}
	fdiscovery.InjectFactory(df)
	return nil
}
<commit_msg>discovery: Add timestamp to discovery.Update.<commit_after>// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build java android

package discovery

import (
	"bytes"
	"encoding/binary"

	"v.io/v23/context"
	"v.io/v23/discovery"

	idiscovery "v.io/x/ref/lib/discovery"
	fdiscovery "v.io/x/ref/lib/discovery/factory"
	"v.io/x/ref/lib/discovery/plugins/mock"

	jutil "v.io/x/jni/util"
)

// JavaDiscovery converts a Go discovery instance into a Java discovery instance.
func JavaDiscovery(env jutil.Env, d discovery.T) (jutil.Object, error) {
	ref := jutil.GoNewRef(&d) // Un-refed when jDiscovery is finalized.
	jDiscovery, err := jutil.NewObject(env, jDiscoveryImplClass, []jutil.Sign{jutil.LongSign}, int64(ref))
	if err != nil {
		jutil.GoDecRef(ref)
		return jutil.NullObject, err
	}
	return jDiscovery, nil
}

// javaUpdate converts a Go update instance into a Java update instance.
func javaUpdate(env jutil.Env, update discovery.Update) (jutil.Object, error) {
	jAd, err := jutil.JVomCopy(env, update.Advertisement(), jAdvertisementClass)
	if err != nil {
		return jutil.NullObject, err
	}
	ref := jutil.GoNewRef(&update) // Un-refed when jUpdate is finalized.
	jUpdate, err := jutil.NewObject(env, jUpdateImplClass, []jutil.Sign{jutil.LongSign, jutil.BoolSign, advertisementSign, jutil.LongSign},
		int64(ref), update.IsLost(), jAd, update.Timestamp().UnixNano())
	if err != nil {
		jutil.GoDecRef(ref)
		return jutil.NullObject, err
	}
	return jUpdate, nil
}

// javaUUID converts a Go UUID into a Java UUID instance.
func javaUUID(env jutil.Env, uuid idiscovery.Uuid) (jutil.Object, error) {
	var high, low int64
	buf := bytes.NewReader(uuid)
	binary.Read(buf, binary.BigEndian, &high)
	binary.Read(buf, binary.BigEndian, &low)
	return jutil.NewObject(env, jUUIDClass, []jutil.Sign{jutil.LongSign, jutil.LongSign}, high, low)
}

// injectMockPlugin injects a discovery factory with a mock plugin into a runtime.
func injectMockPlugin(ctx *context.T) error {
	df, err := idiscovery.NewFactory(ctx, mock.New())
	if err != nil {
		return err
	}
	fdiscovery.InjectFactory(df)
	return nil
}
<|endoftext|>


<commit_before>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package image

import (
	"context"
	"encoding/json"

	"github.com/docker/distribution/manifest/schema2"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"

	"github.com/goharbor/harbor/src/controller/artifact/processor"
	"github.com/goharbor/harbor/src/controller/artifact/processor/base"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/pkg/artifact"
)

// const definitions
const (
	// ArtifactTypeImage is the artifact type for image
	ArtifactTypeImage        = "IMAGE"
	AdditionTypeBuildHistory = "BUILD_HISTORY"
)

func init() {
	pc := &manifestV2Processor{}
	pc.ManifestProcessor = base.NewManifestProcessor()
	mediaTypes := []string{
		v1.MediaTypeImageConfig,
		schema2.MediaTypeImageConfig,
	}
	if err := processor.Register(pc, mediaTypes...); err != nil {
		log.Errorf("failed to register processor for media type %v: %v", mediaTypes, err)
		return
	}
}

// manifestV2Processor processes image with OCI manifest and docker v2 manifest
type manifestV2Processor struct {
	*base.ManifestProcessor
}

func (m *manifestV2Processor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error {
	config := &v1.Image{}
	if err := m.ManifestProcessor.UnmarshalConfig(ctx, artifact.RepositoryName, manifest, config); err != nil {
		return err
	}
	if artifact.ExtraAttrs == nil {
		artifact.ExtraAttrs = map[string]interface{}{}
	}
	artifact.ExtraAttrs["created"] = config.Created
	artifact.ExtraAttrs["architecture"] = config.Architecture
	artifact.ExtraAttrs["os"] = config.OS
	artifact.ExtraAttrs["config"] = config.Config
	// if the author is null, try to get it from labels:
	// https://docs.docker.com/engine/reference/builder/#maintainer-deprecated
	author := config.Author
	if len(author) == 0 && len(config.Config.Labels) > 0 {
		author = config.Config.Labels["maintainer"]
	}
	artifact.ExtraAttrs["author"] = author
	return nil
}

func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
	if addition != AdditionTypeBuildHistory {
		return nil, errors.New(nil).WithCode(errors.BadRequestCode).
			WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeImage)
	}

	mani, _, err := m.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest)
	if err != nil {
		return nil, err
	}
	_, content, err := mani.Payload()
	if err != nil {
		return nil, err
	}
	config := &v1.Image{}
	if err = m.ManifestProcessor.UnmarshalConfig(ctx, artifact.RepositoryName, content, config); err != nil {
		return nil, err
	}
	content, err = json.Marshal(config.History)
	if err != nil {
		return nil, err
	}
	return &processor.Addition{
		Content:     content,
		ContentType: "application/json; charset=utf-8",
	}, nil
}

func (m *manifestV2Processor) GetArtifactType(ctx context.Context, artifact *artifact.Artifact) string {
	return ArtifactTypeImage
}

func (m *manifestV2Processor) ListAdditionTypes(ctx context.Context, artifact *artifact.Artifact) []string {
	return []string{AdditionTypeBuildHistory}
}
<commit_msg>update image extra attributes author in labels (#17291)<commit_after>// Copyright Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package image

import (
	"context"
	"encoding/json"

	"github.com/docker/distribution/manifest/schema2"
	v1 "github.com/opencontainers/image-spec/specs-go/v1"

	"github.com/goharbor/harbor/src/controller/artifact/processor"
	"github.com/goharbor/harbor/src/controller/artifact/processor/base"
	"github.com/goharbor/harbor/src/lib/errors"
	"github.com/goharbor/harbor/src/lib/log"
	"github.com/goharbor/harbor/src/pkg/artifact"
)

// const definitions
const (
	// ArtifactTypeImage is the artifact type for image
	ArtifactTypeImage        = "IMAGE"
	AdditionTypeBuildHistory = "BUILD_HISTORY"
)

func init() {
	pc := &manifestV2Processor{}
	pc.ManifestProcessor = base.NewManifestProcessor()
	mediaTypes := []string{
		v1.MediaTypeImageConfig,
		schema2.MediaTypeImageConfig,
	}
	if err := processor.Register(pc, mediaTypes...); err != nil {
		log.Errorf("failed to register processor for media type %v: %v", mediaTypes, err)
		return
	}
}

// manifestV2Processor processes image with OCI manifest and docker v2 manifest
type manifestV2Processor struct {
	*base.ManifestProcessor
}

func (m *manifestV2Processor) AbstractMetadata(ctx context.Context, artifact *artifact.Artifact, manifest []byte) error {
	config := &v1.Image{}
	if err := m.ManifestProcessor.UnmarshalConfig(ctx, artifact.RepositoryName, manifest, config); err != nil {
		return err
	}
	if artifact.ExtraAttrs == nil {
		artifact.ExtraAttrs = map[string]interface{}{}
	}
	artifact.ExtraAttrs["created"] = config.Created
	artifact.ExtraAttrs["architecture"] = config.Architecture
	artifact.ExtraAttrs["os"] = config.OS
	artifact.ExtraAttrs["config"] = config.Config

	author := config.Author

	// if the author is null, try to get it from labels:
	// https://docs.docker.com/engine/reference/builder/#maintainer-deprecated
	// https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
	authorlist := []string{"org.opencontainers.image.authors", "maintainer"}
	if len(author) == 0 && len(config.Config.Labels) > 0 {
		for _, authorlabel := range authorlist {
			if val, ok := config.Config.Labels[string(authorlabel)]; ok && len(val) > 0 {
				author = val
				break
			}
		}
	}
	artifact.ExtraAttrs["author"] = author
	return nil
}

func (m *manifestV2Processor) AbstractAddition(ctx context.Context, artifact *artifact.Artifact, addition string) (*processor.Addition, error) {
	if addition != AdditionTypeBuildHistory {
		return nil, errors.New(nil).WithCode(errors.BadRequestCode).
			WithMessage("addition %s isn't supported for %s(manifest version 2)", addition, ArtifactTypeImage)
	}

	mani, _, err := m.RegCli.PullManifest(artifact.RepositoryName, artifact.Digest)
	if err != nil {
		return nil, err
	}
	_, content, err := mani.Payload()
	if err != nil {
		return nil, err
	}
	config := &v1.Image{}
	if err = m.ManifestProcessor.UnmarshalConfig(ctx, artifact.RepositoryName, content, config); err != nil {
		return nil, err
	}
	content, err = json.Marshal(config.History)
	if err != nil {
		return nil, err
	}
	return &processor.Addition{
		Content:     content,
		ContentType: "application/json; charset=utf-8",
	}, nil
}

func (m *manifestV2Processor) GetArtifactType(ctx context.Context, artifact *artifact.Artifact) string {
	return ArtifactTypeImage
}

func (m *manifestV2Processor) ListAdditionTypes(ctx context.Context, artifact *artifact.Artifact) []string {
	return []string{AdditionTypeBuildHistory}
}
<|endoftext|>


<commit_before>package color

import (
	"fmt"
	"os"
	"strconv"
	"strings"

	"github.com/mattn/go-colorable"
	"github.com/mattn/go-isatty"
)

// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control
// over each color block use the methods DisableColor() individually.
var NoColor = !isatty.IsTerminal(os.Stdout.Fd())

// Color defines a custom color object which is defined by SGR parameters.
type Color struct {
	params  []Attribute
	noColor *bool
}

// Attribute defines a single SGR Code
type Attribute int

const escape = "\x1b"

// Base attributes
const (
	Reset Attribute = iota
	Bold
	Faint
	Italic
	Underline
	BlinkSlow
	BlinkRapid
	ReverseVideo
	Concealed
	CrossedOut
)

// Foreground text colors
const (
	FgBlack Attribute = iota + 30
	FgRed
	FgGreen
	FgYellow
	FgBlue
	FgMagenta
	FgCyan
	FgWhite
)

// Foreground Hi-Intensity text colors
const (
	FgHiBlack Attribute = iota + 90
	FgHiRed
	FgHiGreen
	FgHiYellow
	FgHiBlue
	FgHiMagenta
	FgHiCyan
	FgHiWhite
)

// Background text colors
const (
	BgBlack Attribute = iota + 40
	BgRed
	BgGreen
	BgYellow
	BgBlue
	BgMagenta
	BgCyan
	BgWhite
)

// Background Hi-Intensity text colors
const (
	BgHiBlack Attribute = iota + 100
	BgHiRed
	BgHiGreen
	BgHiYellow
	BgHiBlue
	BgHiMagenta
	BgHiCyan
	BgHiWhite
)

// New returns a newly created color object.
func New(value ...Attribute) *Color {
	c := &Color{params: make([]Attribute, 0)}
	c.Add(value...)
	return c
}

// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
	c := New(p...)
	c.Set()
	return c
}

// Unset resets all escape attributes and clears the output. Usually should
// be called after Set().
func Unset() {
	if NoColor {
		return
	}

	fmt.Fprintf(Output, "%s[%dm", escape, Reset)
}

// Set sets the SGR sequence.
func (c *Color) Set() *Color {
	if c.isNoColorSet() {
		return c
	}

	fmt.Fprintf(Output, c.format())
	return c
}

func (c *Color) unset() {
	if c.isNoColorSet() {
		return
	}

	Unset()
}

// Add is used to chain SGR parameters. Use as many as parameters to combine
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
func (c *Color) Add(value ...Attribute) *Color {
	c.params = append(c.params, value...)
	return c
}

func (c *Color) prepend(value Attribute) {
	c.params = append(c.params, 0)
	copy(c.params[1:], c.params[0:])
	c.params[0] = value
}

// Output defines the standard output of the print functions. By default
// os.Stdout is used.
var Output = colorable.NewColorableStdout()

// Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a
// string. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Print(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprint(Output, a...)
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
// This is the standard fmt.Printf() method wrapped with the given color.
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintf(Output, format, a...)
}

// Println formats using the default formats for its operands and writes to
// standard output. Spaces are always added between operands and a newline is
// appended. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintln(Output, a...)
}

// PrintFunc returns a new function that prints the passed arguments as
// colorized with color.Print().
func (c *Color) PrintFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Print(a...) }
}

// PrintfFunc returns a new function that prints the passed arguments as
// colorized with color.Printf().
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
	return func(format string, a ...interface{}) { c.Printf(format, a...) }
}

// PrintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Println().
func (c *Color) PrintlnFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Println(a...) }
}

// SprintFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprint(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output, example:
//
//	put := New(FgYellow).SprintFunc()
//	fmt.Fprintf(color.Output, "This is a %s", put("warning"))
func (c *Color) SprintFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprint(a...))
	}
}

// SprintfFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
	return func(format string, a ...interface{}) string {
		return c.wrap(fmt.Sprintf(format, a...))
	}
}

// SprintlnFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprintln(a...))
	}
}

// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
// an example output might be: "1;36" -> bold cyan
func (c *Color) sequence() string {
	format := make([]string, len(c.params))
	for i, v := range c.params {
		format[i] = strconv.Itoa(int(v))
	}

	return strings.Join(format, ";")
}

// wrap wraps the s string with the colors attributes. The string is ready to
// be printed.
func (c *Color) wrap(s string) string {
	if c.isNoColorSet() {
		return s
	}

	return c.format() + s + c.unformat()
}

func (c *Color) format() string {
	return fmt.Sprintf("%s[%sm", escape, c.sequence())
}

func (c *Color) unformat() string {
	return fmt.Sprintf("%s[%dm", escape, Reset)
}

// DisableColor disables the color output. Useful to not change any existing
// code and still being able to output. Can be used for flags like
// "--no-color". To enable back use EnableColor() method.
func (c *Color) DisableColor() {
	c.noColor = boolPtr(true)
}

// EnableColor enables the color output. Use it in conjuction with
// DisableColor(). Otherwise this method has no side effects.
func (c *Color) EnableColor() {
	c.noColor = boolPtr(false)
}

func (c *Color) isNoColorSet() bool {
	// check first if we have user setted action
	if c.noColor != nil {
		return *c.noColor
	}

	// if not return the global option, which is disabled by default
	return NoColor
}

// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
	if len(c.params) != len(c2.params) {
		return false
	}

	for _, attr := range c.params {
		if !c2.attrExists(attr) {
			return false
		}
	}

	return true
}

func (c *Color) attrExists(a Attribute) bool {
	for _, attr := range c.params {
		if attr == a {
			return true
		}
	}

	return false
}

func boolPtr(v bool) *bool {
	return &v
}

// Black is an convenient helper function to print with black foreground. A
// newline is appended to format by default.
func Black(format string, a ...interface{}) { printColor(format, FgBlack, a...) }

// Red is an convenient helper function to print with red foreground. A
// newline is appended to format by default.
func Red(format string, a ...interface{}) { printColor(format, FgRed, a...) }

// Green is an convenient helper function to print with green foreground. A
// newline is appended to format by default.
func Green(format string, a ...interface{}) { printColor(format, FgGreen, a...) }

// Yellow is an convenient helper function to print with yellow foreground.
// A newline is appended to format by default.
func Yellow(format string, a ...interface{}) { printColor(format, FgYellow, a...) }

// Blue is an convenient helper function to print with blue foreground. A
// newline is appended to format by default.
func Blue(format string, a ...interface{}) { printColor(format, FgBlue, a...) }

// Magenta is an convenient helper function to print with magenta foreground.
// A newline is appended to format by default.
func Magenta(format string, a ...interface{}) { printColor(format, FgMagenta, a...) }

// Cyan is an convenient helper function to print with cyan foreground. A
// newline is appended to format by default.
func Cyan(format string, a ...interface{}) { printColor(format, FgCyan, a...) }

// White is an convenient helper function to print with white foreground. A
// newline is appended to format by default.
func White(format string, a ...interface{}) { printColor(format, FgWhite, a...) }

func printColor(format string, p Attribute, a ...interface{}) {
	if len(a) == 0 {
		a = append(a, format)
		format = "%s"
	}

	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}

	c := &Color{params: []Attribute{p}}
	c.Printf(format, a...)
}

func printString(format string, p Attribute, a ...interface{}) string {
	if len(a) == 0 {
		a = append(a, format)
		format = "%s"
	}
	return New(p).SprintfFunc()(format, a...)
}

// BlackString is an convenient helper function to return a string with black
// foreground.
func BlackString(format string, a ...interface{}) string { return printString(format, FgBlack, a...) }

// RedString is an convenient helper function to return a string with red
// foreground.
func RedString(format string, a ...interface{}) string { return printString(format, FgRed, a...) }

// GreenString is an convenient helper function to return a string with green
// foreground.
func GreenString(format string, a ...interface{}) string { return printString(format, FgGreen, a...) }

// YellowString is an convenient helper function to return a string with yellow
// foreground.
func YellowString(format string, a ...interface{}) string { return printString(format, FgYellow, a...) }

// BlueString is an convenient helper function to return a string with blue
// foreground.
func BlueString(format string, a ...interface{}) string { return printString(format, FgBlue, a...) }

// MagentaString is an convenient helper function to return a string with magenta
// foreground.
func MagentaString(format string, a ...interface{}) string {
	return printString(format, FgMagenta, a...)
}

// CyanString is an convenient helper function to return a string with cyan
// foreground.
func CyanString(format string, a ...interface{}) string { return printString(format, FgCyan, a...) }

// WhiteString is an convenient helper function to return a string with white
// foreground.
func WhiteString(format string, a ...interface{}) string { return printString(format, FgWhite, a...) }
<commit_msg>Use color cache map (#43)<commit_after>package color

import (
	"fmt"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/mattn/go-colorable"
	"github.com/mattn/go-isatty"
)

// NoColor defines if the output is colorized or not. It's dynamically set to
// false or true based on the stdout's file descriptor referring to a terminal
// or not. This is a global option and affects all colors. For more control
// over each color block use the methods DisableColor() individually.
var NoColor = !isatty.IsTerminal(os.Stdout.Fd())

// Color defines a custom color object which is defined by SGR parameters.
type Color struct {
	params  []Attribute
	noColor *bool
}

// Attribute defines a single SGR Code
type Attribute int

const escape = "\x1b"

// Base attributes
const (
	Reset Attribute = iota
	Bold
	Faint
	Italic
	Underline
	BlinkSlow
	BlinkRapid
	ReverseVideo
	Concealed
	CrossedOut
)

// Foreground text colors
const (
	FgBlack Attribute = iota + 30
	FgRed
	FgGreen
	FgYellow
	FgBlue
	FgMagenta
	FgCyan
	FgWhite
)

// Foreground Hi-Intensity text colors
const (
	FgHiBlack Attribute = iota + 90
	FgHiRed
	FgHiGreen
	FgHiYellow
	FgHiBlue
	FgHiMagenta
	FgHiCyan
	FgHiWhite
)

// Background text colors
const (
	BgBlack Attribute = iota + 40
	BgRed
	BgGreen
	BgYellow
	BgBlue
	BgMagenta
	BgCyan
	BgWhite
)

// Background Hi-Intensity text colors
const (
	BgHiBlack Attribute = iota + 100
	BgHiRed
	BgHiGreen
	BgHiYellow
	BgHiBlue
	BgHiMagenta
	BgHiCyan
	BgHiWhite
)

// New returns a newly created color object.
func New(value ...Attribute) *Color {
	c := &Color{params: make([]Attribute, 0)}
	c.Add(value...)
	return c
}

// Set sets the given parameters immediately. It will change the color of
// output with the given SGR parameters until color.Unset() is called.
func Set(p ...Attribute) *Color {
	c := New(p...)
	c.Set()
	return c
}

// Unset resets all escape attributes and clears the output. Usually should
// be called after Set().
func Unset() {
	if NoColor {
		return
	}

	fmt.Fprintf(Output, "%s[%dm", escape, Reset)
}

// Set sets the SGR sequence.
func (c *Color) Set() *Color {
	if c.isNoColorSet() {
		return c
	}

	fmt.Fprintf(Output, c.format())
	return c
}

func (c *Color) unset() {
	if c.isNoColorSet() {
		return
	}

	Unset()
}

// Add is used to chain SGR parameters. Use as many as parameters to combine
// and create custom color objects. Example: Add(color.FgRed, color.Underline).
func (c *Color) Add(value ...Attribute) *Color {
	c.params = append(c.params, value...)
	return c
}

func (c *Color) prepend(value Attribute) {
	c.params = append(c.params, 0)
	copy(c.params[1:], c.params[0:])
	c.params[0] = value
}

// Output defines the standard output of the print functions. By default
// os.Stdout is used.
var Output = colorable.NewColorableStdout()

// Print formats using the default formats for its operands and writes to
// standard output. Spaces are added between operands when neither is a
// string. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Print(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprint(Output, a...)
}

// Printf formats according to a format specifier and writes to standard output.
// It returns the number of bytes written and any write error encountered.
// This is the standard fmt.Printf() method wrapped with the given color.
func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintf(Output, format, a...)
}

// Println formats using the default formats for its operands and writes to
// standard output. Spaces are always added between operands and a newline is
// appended. It returns the number of bytes written and any write error
// encountered. This is the standard fmt.Print() method wrapped with the given
// color.
func (c *Color) Println(a ...interface{}) (n int, err error) {
	c.Set()
	defer c.unset()

	return fmt.Fprintln(Output, a...)
}

// PrintFunc returns a new function that prints the passed arguments as
// colorized with color.Print().
func (c *Color) PrintFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Print(a...) }
}

// PrintfFunc returns a new function that prints the passed arguments as
// colorized with color.Printf().
func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
	return func(format string, a ...interface{}) { c.Printf(format, a...) }
}

// PrintlnFunc returns a new function that prints the passed arguments as
// colorized with color.Println().
func (c *Color) PrintlnFunc() func(a ...interface{}) {
	return func(a ...interface{}) { c.Println(a...) }
}

// SprintFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprint(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output, example:
//
//	put := New(FgYellow).SprintFunc()
//	fmt.Fprintf(color.Output, "This is a %s", put("warning"))
func (c *Color) SprintFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprint(a...))
	}
}

// SprintfFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintf(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
	return func(format string, a ...interface{}) string {
		return c.wrap(fmt.Sprintf(format, a...))
	}
}

// SprintlnFunc returns a new function that returns colorized strings for the
// given arguments with fmt.Sprintln(). Useful to put into or mix into other
// string. Windows users should use this in conjuction with color.Output.
func (c *Color) SprintlnFunc() func(a ...interface{}) string {
	return func(a ...interface{}) string {
		return c.wrap(fmt.Sprintln(a...))
	}
}

// sequence returns a formated SGR sequence to be plugged into a "\x1b[...m"
// an example output might be: "1;36" -> bold cyan
func (c *Color) sequence() string {
	format := make([]string, len(c.params))
	for i, v := range c.params {
		format[i] = strconv.Itoa(int(v))
	}

	return strings.Join(format, ";")
}

// wrap wraps the s string with the colors attributes. The string is ready to
// be printed.
func (c *Color) wrap(s string) string {
	if c.isNoColorSet() {
		return s
	}

	return c.format() + s + c.unformat()
}

func (c *Color) format() string {
	return fmt.Sprintf("%s[%sm", escape, c.sequence())
}

func (c *Color) unformat() string {
	return fmt.Sprintf("%s[%dm", escape, Reset)
}

// DisableColor disables the color output. Useful to not change any existing
// code and still being able to output. Can be used for flags like
// "--no-color". To enable back use EnableColor() method.
func (c *Color) DisableColor() {
	c.noColor = boolPtr(true)
}

// EnableColor enables the color output. Use it in conjuction with
// DisableColor(). Otherwise this method has no side effects.
func (c *Color) EnableColor() {
	c.noColor = boolPtr(false)
}

func (c *Color) isNoColorSet() bool {
	// check first if we have user setted action
	if c.noColor != nil {
		return *c.noColor
	}

	// if not return the global option, which is disabled by default
	return NoColor
}

// Equals returns a boolean value indicating whether two colors are equal.
func (c *Color) Equals(c2 *Color) bool {
	if len(c.params) != len(c2.params) {
		return false
	}

	for _, attr := range c.params {
		if !c2.attrExists(attr) {
			return false
		}
	}

	return true
}

func (c *Color) attrExists(a Attribute) bool {
	for _, attr := range c.params {
		if attr == a {
			return true
		}
	}

	return false
}

func boolPtr(v bool) *bool {
	return &v
}

// colorsCache is used to reduce the count of created Color objects and
// allows to reuse already created objects with required Attribute.
var colorsCache = make(map[Attribute]*Color)

var colorsCacheMu = new(sync.Mutex) // protects colorsCache

func getCachedColor(p Attribute) *Color {
	colorsCacheMu.Lock()
	defer colorsCacheMu.Unlock()

	c, ok := colorsCache[p]
	if !ok {
		c = New(p)
		colorsCache[p] = c
	}

	return c
}

func printColor(format string, p Attribute, a ...interface{}) {
	c := getCachedColor(p)

	if len(a) == 0 {
		a = append(a, format)
		format = "%s"
	}

	if !strings.HasSuffix(format, "\n") {
		format += "\n"
	}

	c.Printf(format, a...)
}

func printString(format string, p Attribute, a ...interface{}) string {
	c := getCachedColor(p)

	if len(a) == 0 {
		a = append(a, format)
		format = "%s"
	}

	return c.SprintfFunc()(format, a...)
}

// Black is an convenient helper function to print with black foreground. A
// newline is appended to format by default.
func Black(format string, a ...interface{}) { printColor(format, FgBlack, a...) }

// Red is an convenient helper function to print with red foreground. A
// newline is appended to format by default.
func Red(format string, a ...interface{}) { printColor(format, FgRed, a...) }

// Green is an convenient helper function to print with green foreground. A
// newline is appended to format by default.
func Green(format string, a ...interface{}) { printColor(format, FgGreen, a...) }

// Yellow is an convenient helper function to print with yellow foreground.
// A newline is appended to format by default.
func Yellow(format string, a ...interface{}) { printColor(format, FgYellow, a...) }

// Blue is an convenient helper function to print with blue foreground. A
// newline is appended to format by default.
func Blue(format string, a ...interface{}) { printColor(format, FgBlue, a...) }

// Magenta is an convenient helper function to print with magenta foreground.
// A newline is appended to format by default.
func Magenta(format string, a ...interface{}) { printColor(format, FgMagenta, a...) }

// Cyan is an convenient helper function to print with cyan foreground. A
// newline is appended to format by default.
func Cyan(format string, a ...interface{}) { printColor(format, FgCyan, a...) }

// White is an convenient helper function to print with white foreground. A
// newline is appended to format by default.
func White(format string, a ...interface{}) { printColor(format, FgWhite, a...) }

// BlackString is an convenient helper function to return a string with black
// foreground.
func BlackString(format string, a ...interface{}) string { return printString(format, FgBlack, a...) }

// RedString is an convenient helper function to return a string with red
// foreground.
func RedString(format string, a ...interface{}) string { return printString(format, FgRed, a...) }

// GreenString is an convenient helper function to return a string with green
// foreground.
func GreenString(format string, a ...interface{}) string { return printString(format, FgGreen, a...) }

// YellowString is an convenient helper function to return a string with yellow
// foreground.
func YellowString(format string, a ...interface{}) string { return printString(format, FgYellow, a...) }

// BlueString is an convenient helper function to return a string with blue
// foreground.
func BlueString(format string, a ...interface{}) string { return printString(format, FgBlue, a...) }

// MagentaString is an convenient helper function to return a string with magenta
// foreground.
func MagentaString(format string, a ...interface{}) string {
	return printString(format, FgMagenta, a...)
}

// CyanString is an convenient helper function to return a string with cyan
// foreground.
func CyanString(format string, a ...interface{}) string { return printString(format, FgCyan, a...) }

// WhiteString is an convenient helper function to return a string with white
// foreground.
func WhiteString(format string, a ...interface{}) string { return printString(format, FgWhite, a...) }
<|endoftext|>


<commit_before>// Package csv provides processing of csv files
package csv

import (
  gocsv "encoding/csv"
  "errors"
  "fmt"
  "github.com/keep94/finance/fin"
  "github.com/keep94/finance/fin/autoimport"
  "github.com/keep94/finance/fin/autoimport/qfx"
  "github.com/keep94/finance/fin/autoimport/qfx/qfxdb"
  "hash/fnv"
  "io"
  "strconv"
  "time"
)

// CsvLoader implements the autoimport.Loader interface for csv files.
type CsvLoader struct {
  // Store stores which transactions have already been processed.
  Store qfxdb.Store
}

func (c CsvLoader) Load(
    accountId int64,
    bankAccountId string,
    r io.Reader,
    startDate time.Time) (autoimport.Batch, error) {
  reader := gocsv.NewReader(r)
  line, err := reader.Read()
  if err != nil {
    return nil, err
  }
  parseit := fromHeader(line)
  if parseit == nil {
    return nil, errors.New("Unrecognized csv header")
  }
  var result []*qfx.QfxEntry
  for line, err = reader.Read(); err == nil; line, err = reader.Read() {
    var qentry qfx.QfxEntry
    var ok bool
    ok, err = parseit(line, accountId, &qentry.Entry)
    if err != nil {
      return nil, err
    }
    if !ok || qentry.Date.Before(startDate) {
      continue
    }
    qentry.FitId, err = generateFitId(line)
    if err != nil {
      return nil, err
    }
    result = append(result, &qentry)
  }
  if err != io.EOF {
    return nil, err
  }
  return &qfx.QfxBatch{Store: c.Store, AccountId: accountId, QfxEntries: result}, nil
}

func fromPaypalHeader(line []string, accountId int64, entry *fin.Entry) (ok bool, err error) {
  entry.Date, err = time.Parse("1/2/2006", line[0])
  if err != nil {
    return
  }
  entry.Name = line[3]
  if entry.Name == "Bank Account" {
    return
  }
  var amt int64
  amt, err = fin.ParseUSD(line[6])
  if err != nil {
    return
  }
  entry.CatPayment = fin.NewCatPayment(fin.Expense, -amt, true, accountId)
  ok = true
  return
}

func fromHeader(line []string) func([]string, int64, *fin.Entry) (bool, error) {
  if len(line) == 10 && line[0] == "Date" && line[3] == " Name" && line[6] == " Amount" {
    return fromPaypalHeader
  }
  return nil
}

func generateFitId(line []string) (string, error) {
  h := fnv.New64a()
  s := fmt.Sprintf("%v", line)
  _, err := h.Write(([]byte)(s))
  if err != nil {
    return "", err
  }
  return strconv.FormatUint(h.Sum64(), 10), nil
}
<commit_msg>Support importing CSV from this application.<commit_after>// Package csv provides processing of csv files
package csv

import (
  gocsv "encoding/csv"
  "errors"
  "fmt"
  "github.com/keep94/finance/fin"
  "github.com/keep94/finance/fin/autoimport"
  "github.com/keep94/finance/fin/autoimport/qfx"
  "github.com/keep94/finance/fin/autoimport/qfx/qfxdb"
  "hash/fnv"
  "io"
  "strconv"
  "time"
)

// CsvLoader implements the autoimport.Loader interface for csv files.
type CsvLoader struct {
  // Store stores which transactions have already been processed.
  Store qfxdb.Store
}

func (c CsvLoader) Load(
    accountId int64,
    bankAccountId string,
    r io.Reader,
    startDate time.Time) (autoimport.Batch, error) {
  reader := gocsv.NewReader(r)
  line, err := reader.Read()
  if err != nil {
    return nil, err
  }
  parseit := fromHeader(line)
  if parseit == nil {
    return nil, errors.New("Unrecognized csv header")
  }
  var result []*qfx.QfxEntry
  for line, err = reader.Read(); err == nil; line, err = reader.Read() {
    var qentry qfx.QfxEntry
    var ok bool
    ok, err = parseit(line, accountId, &qentry.Entry)
    if err != nil {
      return nil, err
    }
    if !ok || qentry.Date.Before(startDate) {
      continue
    }
    qentry.FitId, err = generateFitId(line)
    if err != nil {
      return nil, err
    }
    result = append(result, &qentry)
  }
  if err != io.EOF {
    return nil, err
  }
  return &qfx.QfxBatch{Store: c.Store, AccountId: accountId, QfxEntries: result}, nil
}

func fromNativeHeader(line []string, accountId int64, entry *fin.Entry) (ok bool, err error) {
  entry.Date, err = time.Parse("1/2/2006", line[0])
  if err != nil {
    return
  }
  entry.CheckNo = line[1]
  entry.Name = line[2]
  entry.Desc = line[3]
  var amt int64
  amt, err = fin.ParseUSD(line[4])
  if err != nil {
    return
  }
  entry.CatPayment = fin.NewCatPayment(fin.Expense, -amt, true, accountId)
  ok = true
  return
}

func fromPaypalHeader(line []string, accountId int64, entry *fin.Entry) (ok bool, err error) {
  entry.Date, err = time.Parse("1/2/2006", line[0])
  if err != nil {
    return
  }
  entry.Name = line[3]
  if entry.Name == "Bank Account" {
    return
  }
  var amt int64
  amt, err = fin.ParseUSD(line[6])
  if err != nil {
    return
  }
  entry.CatPayment = fin.NewCatPayment(fin.Expense, -amt, true, accountId)
  ok = true
  return
}

func fromHeader(line []string) func([]string, int64, *fin.Entry) (bool, error) {
  if len(line) == 10 && line[0] == "Date" && line[3] == " Name" && line[6] == " Amount" {
    return fromPaypalHeader
  }
  if len(line) == 5 && line[0] == "Date" && line[1] == "CheckNo" && line[2] == "Name" && line[3] == "Desc" && line[4] == "Amount" {
    return fromNativeHeader
  }
  return nil
}

func generateFitId(line []string) (string, error) {
  h := fnv.New64a()
  s := fmt.Sprintf("%v", line)
  _, err := h.Write(([]byte)(s))
  if err != nil {
    return "", err
  }
  return strconv.FormatUint(h.Sum64(), 10), nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package main

import (
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/coreos/go-etcd/etcd"
	"github.com/kelseyhightower/go-ini"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"text/template"
)

type FileInfo struct {
	Uid  uint32
	Gid  uint32
	Mode uint16
	Md5  string
}

type Settings struct {
	ConfigDir  string
	EtcdURL    string
	EtcdPrefix string
}

type Config struct {
	Templates []Template
	Services  map[string]Service
}

type Service struct {
	Name      string
	ReloadCmd string `json:"reload_cmd"`
}

type Template struct {
	Dest    string
	Gid     int
	Keys    []string
	Mode    string
	Uid     int
	Service string
	Src     string
}

var settings Settings
var defaultConfig = "/etc/confd/confd.ini"

func main() {
	if err := setConfig(); err != nil {
		log.Fatal(err.Error())
	}
	c := etcd.NewClient()

	// Get config
	configs, err := filepath.Glob(filepath.Join(settings.ConfigDir, "*json"))
	if err != nil {
		log.Fatal(err.Error())
	}

	for _, config := range configs {
		f, err := ioutil.ReadFile(config)
		if err != nil {
			log.Fatal(err.Error())
		}

		var cfg *Config
		err = json.Unmarshal(f, &cfg)
		if err != nil {
			log.Fatal(err.Error())
		}

		// Get the values we care about.
		r := strings.NewReplacer("/", "_")
		for _, tmplConfig := range cfg.Templates {
			m := make(map[string]interface{})
			for _, key := range tmplConfig.Keys {
				values, err := c.Get(filepath.Join(settings.EtcdPrefix, key))
				if err != nil {
					log.Fatal(err.Error())
				}
				for _, v := range values {
					key := strings.TrimPrefix(v.Key, settings.EtcdPrefix)
					new_key := r.Replace(key)
					m[new_key] = v.Value
				}
			}
			tmpl := filepath.Join(settings.ConfigDir, "templates", tmplConfig.Src)
			if isFileExist(tmpl) {
				temp, err := ioutil.TempFile("", "")
				defer os.Remove(temp.Name())
				if err != nil {
					log.Fatal(err.Error())
				}

				data, err := ioutil.ReadFile(tmpl)
				if err != nil {
					log.Fatal(err.Error())
				}
				t := template.Must(template.New("test").Parse(string(data)))
				err = t.Execute(temp, m)
				if err != nil {
					log.Fatal(err.Error())
				}
				myMode, _ := strconv.ParseUint(tmplConfig.Mode, 0, 32)
				os.Chmod(temp.Name(), os.FileMode(myMode))
				os.Chown(temp.Name(), tmplConfig.Uid, tmplConfig.Gid)

				if isSync(temp.Name(), tmplConfig.Dest) {
					log.Print("Files are in sync")
				} else {
					log.Print("File not in sync")
					os.Rename(temp.Name(), tmplConfig.Dest)
					cmd := cfg.Services[tmplConfig.Service].ReloadCmd
					log.Printf("Running %s", cmd)
				}
			} else {
				log.Fatal("Missing template: " + tmpl)
			}
		}
	}
}

func Stat(name string) (fi FileInfo, err error) {
	if isFileExist(name) {
		f, err := os.Open(name)
		defer f.Close()
		if err != nil {
			log.Fatal(err.Error())
		}
		stats, _ := f.Stat()
		fi.Uid = stats.Sys().(*syscall.Stat_t).Uid
		fi.Gid = stats.Sys().(*syscall.Stat_t).Gid
		fi.Mode = stats.Sys().(*syscall.Stat_t).Mode
		h := md5.New()
		io.Copy(h, f)
		fi.Md5 = fmt.Sprintf("%x", h.Sum(nil))
		return fi, nil
	} else {
		return fi, errors.New("File not found")
	}
}

func isSync(src, dest string) bool {
	old, err := Stat(dest)
	if err != nil {
		log.Fatal(err.Error())
	}

	n, err := Stat(src)
	if err != nil {
		log.Fatal(err.Error())
	}
	if old.Uid != n.Uid {
		return false
	}
	if old.Gid != n.Gid {
		return false
	}
	if old.Mode != n.Mode {
		return false
	}
	if old.Md5 != n.Md5 {
		return false
	}
	return true
}

func setConfig() error {
	// Set defaults
	settings.ConfigDir = "/etc/confd/conf.d"
	settings.EtcdURL = "http://0.0.0.0:4001"
	settings.EtcdPrefix = "/"

	// Override defaults from config file.
	if isFileExist(defaultConfig) {
		s, err := ini.LoadFile(defaultConfig)
		if err != nil {
			return err
		}
		if configDir, ok := s.Get("main", "config_dir"); ok {
			settings.ConfigDir = configDir
		}
		if etcdURL, ok := s.Get("etcd", "url"); ok {
			settings.EtcdURL = etcdURL
		}
		if etcdPrefix, ok := s.Get("etcd", "prefix"); ok {
			settings.EtcdPrefix = etcdPrefix
		}
	}
	return nil
}

func isFileExist(fpath string) bool {
	if _, err := os.Stat(fpath); os.IsNotExist(err) {
		return false
	}
	return true
}
<commit_msg>Refactor main function<commit_after>// Copyright (c) 2013 Kelsey Hightower. All rights reserved.
// Use of this source code is governed by the Apache License, Version 2.0
// that can be found in the LICENSE file.
package main

import (
	"crypto/md5"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/coreos/go-etcd/etcd"
	"github.com/kelseyhightower/go-ini"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"text/template"
)

type FileInfo struct {
	Uid  uint32
	Gid  uint32
	Mode uint16
	Md5  string
}

type Settings struct {
	ConfigDir  string
	EtcdURL    string
	EtcdPrefix string
}

type Config struct {
	Templates []Template
	Services  map[string]Service
}

type Service struct {
	Name      string
	ReloadCmd string `json:"reload_cmd"`
}

type Template struct {
	Dest    string
	Gid     int
	Keys    []string
	Mode    string
	Uid     int
	Service string
	Src     string
}

var settings Settings
var defaultConfig = "/etc/confd/confd.ini"

func main() {
	if err := setConfig(); err != nil {
		log.Fatal(err.Error())
	}

	// Get config
	configs, err := filepath.Glob(filepath.Join(settings.ConfigDir, "*json"))
	if err != nil {
		log.Fatal(err.Error())
	}

	for _, config := range configs {
		if err := ProcessConfig(config); err != nil {
			log.Println(err.Error())
		}
	}
}

func GetValuesFromEctd(keys []string) (map[string]interface{}, error) {
	c := etcd.NewClient()
	r := strings.NewReplacer("/", "_")
	m := make(map[string]interface{})
	for _, key := range keys {
		values, err := c.Get(filepath.Join(settings.EtcdPrefix, key))
		if err != nil {
			return m, err
		}
		for _, v := range values {
			key := strings.TrimPrefix(v.Key, settings.EtcdPrefix)
			new_key := r.Replace(key)
			m[new_key] = v.Value
		}
	}
	return m, nil
}

func ProcessConfig(config string) error {
	f, err := ioutil.ReadFile(config)
	if err != nil {
		return err
	}

	var cfg *Config
	if err = json.Unmarshal(f, &cfg); err != nil {
		return err
	}

	for _, tmplConfig := range cfg.Templates {
		m, err := GetValuesFromEctd(tmplConfig.Keys)
		if err != nil {
			return err
		}
		tmpl := filepath.Join(settings.ConfigDir, "templates", tmplConfig.Src)
		if isFileExist(tmpl) {
			temp, err := ioutil.TempFile("", "")
			defer os.Remove(temp.Name())
			if err != nil {
				return err
			}

			data, err := ioutil.ReadFile(tmpl)
			if err != nil {
				return err
			}
			t := template.Must(template.New("test").Parse(string(data)))
			err = t.Execute(temp, m)
			if err != nil {
				return err
			}
			myMode, _ := strconv.ParseUint(tmplConfig.Mode, 0, 32)
			os.Chmod(temp.Name(), os.FileMode(myMode))
			os.Chown(temp.Name(), tmplConfig.Uid, tmplConfig.Gid)

			if isSync(temp.Name(), tmplConfig.Dest) {
				log.Print("Files are in sync")
			} else {
				log.Print("File not in sync")
				os.Rename(temp.Name(), tmplConfig.Dest)
				cmd := cfg.Services[tmplConfig.Service].ReloadCmd
				log.Printf("Running %s", cmd)
			}
		} else {
			return errors.New("Missing template: " + tmpl)
		}
	}
	return nil
}

func Stat(name string) (fi FileInfo, err error) {
	if isFileExist(name) {
		f, err := os.Open(name)
		defer f.Close()
		if err != nil {
			return fi, err
		}
		stats, _ := f.Stat()
		fi.Uid = stats.Sys().(*syscall.Stat_t).Uid
		fi.Gid = stats.Sys().(*syscall.Stat_t).Gid
		fi.Mode = stats.Sys().(*syscall.Stat_t).Mode
		h := md5.New()
		io.Copy(h, f)
		fi.Md5 = fmt.Sprintf("%x", h.Sum(nil))
		return fi, nil
	} else {
		return fi, errors.New("File not found")
	}
}

func isSync(src, dest string) bool {
	if ! isFileExist(dest) {
		return false
	}
	old, err := Stat(dest)
	if err != nil {
		log.Fatal(err.Error())
	}

	n, err := Stat(src)
	if err != nil {
		log.Fatal(err.Error())
	}
	if old.Uid != n.Uid {
		return false
	}
	if old.Gid != n.Gid {
		return false
	}
	if old.Mode != n.Mode {
		return false
	}
	if old.Md5 != n.Md5 {
		return false
	}
	return true
}

func setConfig() error {
	// Set defaults
	settings.ConfigDir = "/etc/confd/conf.d"
	settings.EtcdURL = "http://0.0.0.0:4001"
	settings.EtcdPrefix = "/"

	// Override defaults from config file.
	if isFileExist(defaultConfig) {
		s, err := ini.LoadFile(defaultConfig)
		if err != nil {
			return err
		}
		if configDir, ok := s.Get("main", "config_dir"); ok {
			settings.ConfigDir = configDir
		}
		if etcdURL, ok := s.Get("etcd", "url"); ok {
			settings.EtcdURL = etcdURL
		}
		if etcdPrefix, ok := s.Get("etcd", "prefix"); ok {
			settings.EtcdPrefix = etcdPrefix
		}
	}
	return nil
}

func isFileExist(fpath string) bool {
	if _, err := os.Stat(fpath); os.IsNotExist(err) {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>/* This example mirrors the second example in the TLDP ncurses howto,
    demonstrating some of the initilization options for ncurses;
    In gnome, the F1 key launches help, so F2 is tested for instead */

package main

import . "goncurses.googlecode.com/hg/goncurses"

func main() {
    stdscr, _ := Init();
    defer End()

    row, col := stdscr.Maxyx()
    msg := "Just a string "
    stdscr.Print(row/2, (col-len(msg))/2, msg)

    stdscr.Print(row-2, 0, "This screen has %d rows and %d columns. ",
        row, col)
    stdscr.Print("Try resizing your window and then run this program again.")

    stdscr.Refresh()
    stdscr.GetChar()
}
<commit_msg>Update print example and format with go fmt<commit_after>/* This example demonstrates the use of the print function. */

package main

import gc "code.google.com/p/goncurses"

func main() {
	stdscr, _ := gc.Init()
	defer gc.End()

	row, col := stdscr.Maxyx()
	msg := "Just a string "
	stdscr.Print(row/2, (col-len(msg))/2, msg)

	stdscr.Print(row-3, 0, "This screen has %d rows and %d columns. ",
		row, col)
	stdscr.Print(row-2, 0, "Try resizing your window and then run this "+
		"program again.")

	stdscr.Refresh()
	stdscr.GetChar()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/Shopify/sarama"
	"github.com/travisjeffery/jocko/broker"
	"github.com/travisjeffery/jocko/server"
	"github.com/travisjeffery/simplelog"
	kingpin "gopkg.in/alecthomas/kingpin.v2"
)

type check struct {
	partition int32
	offset    int64
	message   string
}

const (
	topic         = "test_topic"
	messageCount  = 15
	clientID      = "test_client"
	numPartitions = int32(8)
)

var (
	logDir   = kingpin.Flag("logdir", "A comma separated list of directories under which to store log files").Default("logdir").String()
	tcpAddr  = kingpin.Flag("tcpAddr", "HTTP Address to listen on").Default(":8000").String()
	raftDir  = kingpin.Flag("raftdir", "Directory for raft to store data").Default("raftdir").String()
	raftAddr = kingpin.Flag("raftaddr", "Address for Raft to bind on").Default(":4000").String()
	brokerID = kingpin.Flag("id", "Broker ID").Int32()
)

func main() {
	kingpin.Parse()

	setup()

	config := sarama.NewConfig()
	config.ChannelBufferSize = 1
	config.Version = sarama.V0_10_0_1
	config.Producer.Return.Successes = true

	brokers := []string{*tcpAddr}
	producer, err := sarama.NewSyncProducer(brokers, config)
	if err != nil {
		panic(err)
	}

	pmap := make(map[int32][]check)

	for i := 0; i < messageCount; i++ {
		message := fmt.Sprintf("Hello from Jocko #%d!", i)
		partition, offset, err := producer.SendMessage(&sarama.ProducerMessage{
			Topic: topic,
			Value: sarama.StringEncoder(message),
		})
		if err != nil {
			panic(err)
		}
		pmap[partition] = append(pmap[partition], check{
			partition: partition,
			offset:    offset,
			message:   message,
		})
	}
	if err = producer.Close(); err != nil {
		panic(err)
	}

	var totalChecked int
	for partitionID := range pmap {
		checked := 0
		consumer, err := sarama.NewConsumer(brokers, config)
		if err != nil {
			panic(err)
		}
		partition, err := consumer.ConsumePartition(topic, partitionID, 0)
		if err != nil {
			panic(err)
		}
		i := 0
		for msg := range partition.Messages() {
			fmt.Printf("msg partition [%d] offset [%d]\n", msg.Partition, msg.Offset)
			check := pmap[partitionID][i]
			if string(msg.Value) != check.message {
				log.Fatalf("msg value not equal! partition %d, offset: %d!\n", msg.Partition, msg.Offset)
			}
			if msg.Offset != check.offset {
				log.Fatalf("msg offset not equal! partition %d, offset: %d!\n", msg.Partition, msg.Offset)
			}
			log.Printf("msg is ok! partition: %d, offset: %d\n", msg.Partition, msg.Offset)
			i++
			checked++
			fmt.Printf("i: %d, len: %d\n", i, len(pmap[partitionID]))
			if i == len(pmap[partitionID]) {
				totalChecked += checked
				fmt.Println("checked partition:", partitionID)
				if err = consumer.Close(); err != nil {
					panic(err)
				}
				break
			} else {
				fmt.Println("still checking partition:", partitionID)
			}
		}
	}
	fmt.Printf("producer and consumer worked! %d messages ok\n", totalChecked)
}

func setup() {
	logger := simplelog.New(os.Stdout, simplelog.INFO, "jocko")

	store, err := broker.New(*brokerID,
		broker.DataDir(*logDir),
		broker.LogDir(*logDir),
		broker.Logger(logger))
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error opening raft store: %s\n", err)
		os.Exit(1)
	}
	server := server.New(*tcpAddr, store, logger)
	if err := server.Start(); err != nil {
		fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
		os.Exit(1)
	}

	if _, err := store.WaitForLeader(10 * time.Second); err != nil {
		panic(err)
	}

	// creating/deleting topic directly since Sarama doesn't support it
	if err := store.CreateTopic(topic, numPartitions); err != nil && err != broker.ErrTopicExists {
		panic(err)
	}
}
<commit_msg>fix sarama example (close #26)<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/Shopify/sarama"
	"github.com/travisjeffery/jocko/broker"
	"github.com/travisjeffery/jocko/server"
	"github.com/travisjeffery/simplelog"
)

type check struct {
	partition int32
	offset    int64
	message   string
}

const (
	topic         = "test_topic"
	messageCount  = 15
	clientID      = "test_client"
	numPartitions = int32(8)
	brokerAddr    = "127.0.0.1:9092"
	raftAddr      = "127.0.0.1:9093"
	serfAddr      = "127.0.0.1:9094"
	logDir        = "logdir"
	brokerID      = 0
)

func main() {
	defer setup()()

	config := sarama.NewConfig()
	config.ChannelBufferSize = 1
	config.Version = sarama.V0_10_0_1
	config.Producer.Return.Successes = true

	brokers := []string{brokerAddr}
	producer, err := sarama.NewSyncProducer(brokers, config)
	if err != nil {
		panic(err)
	}

	pmap := make(map[int32][]check)

	for i := 0; i < messageCount; i++ {
		message := fmt.Sprintf("Hello from Jocko #%d!", i)
		partition, offset, err := producer.SendMessage(&sarama.ProducerMessage{
			Topic: topic,
			Value: sarama.StringEncoder(message),
		})
		if err != nil {
			panic(err)
		}
		pmap[partition] = append(pmap[partition], check{
			partition: partition,
			offset:    offset,
			message:   message,
		})
	}
	if err = producer.Close(); err != nil {
		panic(err)
	}

	var totalChecked int
	for partitionID := range pmap {
		checked := 0
		consumer, err := sarama.NewConsumer(brokers, config)
		if err != nil {
			panic(err)
		}
		partition, err := consumer.ConsumePartition(topic, partitionID, 0)
		if err != nil {
			panic(err)
		}
		i := 0
		for msg := range partition.Messages() {
			fmt.Printf("msg partition [%d] offset [%d]\n", msg.Partition, msg.Offset)
			check := pmap[partitionID][i]
			if string(msg.Value) != check.message {
				log.Fatalf("msg value not equal! partition %d, offset: %d!\n", msg.Partition, msg.Offset)
			}
			if msg.Offset != check.offset {
				log.Fatalf("msg offset not equal! partition %d, offset: %d!\n", msg.Partition, msg.Offset)
			}
			log.Printf("msg is ok! partition: %d, offset: %d\n", msg.Partition, msg.Offset)
			i++
			checked++
			fmt.Printf("i: %d, len: %d\n", i, len(pmap[partitionID]))
			if i == len(pmap[partitionID]) {
				totalChecked += checked
				fmt.Println("checked partition:", partitionID)
				if err = consumer.Close(); err != nil {
					panic(err)
				}
				break
			} else {
				fmt.Println("still checking partition:", partitionID)
			}
		}
	}
	fmt.Printf("producer and consumer worked! %d messages ok\n", totalChecked)
}

func setup() func() {
	logger := simplelog.New(os.Stdout, simplelog.DEBUG, "jocko")

	store, err := broker.New(brokerID,
		broker.DataDir(logDir),
		broker.BrokerAddr(brokerAddr),
		broker.RaftAddr(raftAddr),
		broker.SerfAddr(serfAddr),
		broker.LogDir(logDir),
		broker.Logger(logger))

	if err != nil {
		fmt.Fprintf(os.Stderr, "Error opening raft store: %s\n", err)
		os.Exit(1)
	}
	server := server.New(brokerAddr, store, logger)
	if err := server.Start(); err != nil {
		fmt.Fprintf(os.Stderr, "Error starting server: %s\n", err)
		os.Exit(1)
	}

	if _, err := store.WaitForLeader(10 * time.Second); err != nil {
		panic(err)
	}

	// creating/deleting topic directly since Sarama doesn't support it
	if err := store.CreateTopic(topic, numPartitions); err != nil && err != broker.ErrTopicExists {
		panic(err)
	}

	return func() {
		os.RemoveAll(logDir)
	}
}
<|endoftext|>