<commit_before>//
// templates.go
// template model interfaces
//
package srnd

import (
  "fmt"
  "github.com/hoisie/mustache"
  "io"
  "io/ioutil"
  "log"
  "path/filepath"
  "sort"
  "strings"
)

type templateEngine struct {
  // shorthash -> url
  links map[string]string
  // every newsgroup
  groups map[string]GroupModel
  // loaded templates
  templates map[string]string
  // root directory for templates
  template_dir string
}

func (self templateEngine) templateCached(name string) (ok bool) {
  _, ok = self.templates[name]
  return
}

// explicitly reload a template
func (self templateEngine) reloadTemplate(name string) {
  self.templates[name] = self.loadTemplate(name)
}

// check if we have this template
func (self templateEngine) hasTemplate(name string) bool {
  return CheckFile(self.templateFilepath(name))
}

// explicitly reload all loaded templates
func (self templateEngine) reloadAllTemplates() {
  loadThese := []string{}
  // get all the names of the templates we have loaded
  for tname, _ := range self.templates {
    loadThese = append(loadThese, tname)
  }
  // for each template we have loaded, reload the contents from file
  for _, tname := range loadThese {
    self.reloadTemplate(tname)
  }
}

// update the link -> url cache given our current model
// return new link table
func (self templateEngine) updateLinkCache() (links map[string]string) {
  // clear existing cache
  links = make(map[string]string)

  // for each group
  for _, group := range self.groups {
    // for each page in group
    for _, page := range group {
      // for each thread in page
      for _, thread := range page.Threads() {
        // put op entry
        h := thread.OP().ShortHash()
        u := thread.OP().PostURL()
        links[h] = u
        // for each reply
        for _, p := range thread.Replies() {
          // put reply entry
          h = p.ShortHash()
          u = p.PostURL()
          links[h] = u
        }
      }
    }
  }
  return links
}

// get the url for a backlink
func (self templateEngine) findLink(shorthash string) (url string) {
  log.Println(self.links)
  url, _ = self.links[shorthash]
  return
}

// get the filepath to a template
func (self templateEngine) templateFilepath(name string) string {
  if strings.Count(name, "..") > 0 {
    return ""
  }
  return filepath.Join(self.template_dir, name)
}

// load a template from file, return as string
func (self templateEngine) loadTemplate(name string) (t string) {
  b, err := ioutil.ReadFile(self.templateFilepath(name))
  if err == nil {
    t = string(b)
  } else {
    log.Println("error loading template", err)
    t = err.Error()
  }
  return
}

// get a template, if it's not cached load from file and cache it
func (self templateEngine) getTemplate(name string) (t string) {
  if ! self.templateCached(name) {
    self.templates[name] = self.loadTemplate(name)
  }
  t, _ = self.templates[name]
  return
}

// render a template, self explanitory
func (self templateEngine) renderTemplate(name string, obj interface{}) string {
  t := self.getTemplate(name)
  return mustache.Render(t, obj)
}

// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
  model, ok := self.groups[group]
  // if we don't already have the board loaded load it
  if ! ok  {
    p := db.GetGroupPageCount(group)
    pages := int(p)
    // ignore error
    perpage, _ := db.GetThreadsPerPage(group)
    for page := 0 ; page < pages ; page ++ {
      model = append(model, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
    }
    self.groups[group] = model
  }
  return

}
// generate a board page
func (self templateEngine) genBoardPage(prefix, frontend, newsgroup string, page int, outfile string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the board page
  board = board.Update(page, db)
  if page >= len(board) {
    log.Println("board page should not exist", newsgroup, "page", page)
    return
  }
  // render it
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    board[page].RenderTo(wr)
    wr.Close()
    log.Println("wrote file", outfile)
  } else {
    log.Println("error generating board page", page, "for", newsgroup, err)
  }
  // save it
  self.groups[newsgroup] = board
}

// generate every page for a board
func (self templateEngine) genBoard(prefix, frontend, newsgroup, outdir string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the entire board model
  board = board.UpdateAll(db)
  // save the model
  self.groups[newsgroup] = board
  self.links = self.updateLinkCache()

  pages := len(board)
  for page := 0 ; page < pages ; page ++ {
    outfile := filepath.Join(outdir, fmt.Sprintf("%s-%d.html", newsgroup, page))
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      board[page].RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("error generating board page", page, "for", newsgroup, err)
    }
  }
}

func (self templateEngine) genUkko(prefix, frontend, outfile string, database Database) {
  var threads []ThreadModel
  // get the last 15 bumped threads globally, for each...
  for _, article := range database.GetLastBumpedThreads("", 15) {
    // get the newsgroup and root post id
    newsgroup, msgid := article[1], article[0]
    // obtain board model
    board := self.obtainBoard(prefix, frontend, newsgroup, database)
    // update first page
    board = board.Update(0, database)
    // grab the root post in question
    th := board[0].GetThread(msgid)
    if th != nil {
        threads = append(threads, th.Update(database))
    }
    // save board model
    self.groups[newsgroup] = board
  }
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    self.links = self.updateLinkCache()
    io.WriteString(wr, template.renderTemplate("ukko.mustache", map[string]interface{} { "prefix" : prefix, "threads" : threads }))
    wr.Close()
  } else {
    log.Println("error generating ukko", err)
  }
}

func (self templateEngine) genThread(root ArticleEntry, prefix, frontend, outfile string, db Database) {
  newsgroup := root.Newsgroup()
  msgid := root.MessageID()
  var th ThreadModel
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // find the thread model in question
  for _, pagemodel := range board {
    t := pagemodel.GetThread(msgid)
    if t != nil {
      th = t
      break
    }
  }

  if th == nil {
    // a new thread?
    board[0] = board[0].Update(db)
    t := board[0].GetThread(msgid)
    if t != nil {
      th = t
    }
  }

  if th == nil {
    log.Println("we didn't find thread for", msgid, "did not regenerate")
  } else {
    // update thread model and write it out
    th = th.Update(db)
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      self.links = self.updateLinkCache()
      th.RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("did not write", outfile, err)
    }
  }
  // save it
  self.groups[newsgroup] = board
}

func newTemplateEngine(dir string) *templateEngine {
  return &templateEngine{
    groups: make(map[string]GroupModel),
    templates: make(map[string]string),
    template_dir: dir,
  }
}

var template = newTemplateEngine(defaultTemplateDir())

func renderPostForm(prefix, board, op_msg_id string) string {
  url := prefix + "post/" + board
  button := "New Thread"
  if op_msg_id != "" {
    button = "Reply"
  }
  return template.renderTemplate("postform.mustache", map[string]string { "post_url" : url, "reference" : op_msg_id , "button" : button } )
}

func (self templateEngine) genFrontPage(top_count int, frontend_name, outfile string, db Database) {
  // the graph for the front page
  var frontpage_graph boardPageRows

  // for each group
  groups := db.GetAllNewsgroups()
  for _, group := range groups {
    // posts per hour
    hour := db.CountPostsInGroup(group, 3600)
    // posts per day
    day := db.CountPostsInGroup(group, 86400)
    // posts total
    all := db.CountPostsInGroup(group, 0)
    frontpage_graph = append(frontpage_graph, boardPageRow{
      All: all,
      Day: day,
      Hour: hour,
      Board: group,
    })
  }
  wr, err := OpenFileWriter(outfile)
  if err != nil {
    log.Println("cannot render front page", err)
    return
  }

  param := make(map[string]interface{})
  sort.Sort(frontpage_graph)
  param["graph"] = frontpage_graph
  param["frontend"] = frontend_name
  param["totalposts"] = db.ArticleCount()
  _, err = io.WriteString(wr, self.renderTemplate("frontpage.mustache", param))
  if err != nil {
    log.Println("error writing front page", err)
  }
  wr.Close()
}
<commit_msg>use global variable for template cache<commit_after>//
// templates.go
// template model interfaces
//
package srnd

import (
  "fmt"
  "github.com/hoisie/mustache"
  "io"
  "io/ioutil"
  "log"
  "path/filepath"
  "sort"
  "strings"
)

type templateEngine struct {
  // shorthash -> url
  links map[string]string
  // every newsgroup
  groups map[string]GroupModel
  // loaded templates
  templates map[string]string
  // root directory for templates
  template_dir string
}

func (self templateEngine) templateCached(name string) (ok bool) {
  _, ok = self.templates[name]
  return
}

// explicitly reload a template
func (self templateEngine) reloadTemplate(name string) {
  self.templates[name] = self.loadTemplate(name)
}

// check if we have this template
func (self templateEngine) hasTemplate(name string) bool {
  return CheckFile(self.templateFilepath(name))
}

// explicitly reload all loaded templates
func (self templateEngine) reloadAllTemplates() {
  loadThese := []string{}
  // get all the names of the templates we have loaded
  for tname, _ := range self.templates {
    loadThese = append(loadThese, tname)
  }
  // for each template we have loaded, reload the contents from file
  for _, tname := range loadThese {
    self.reloadTemplate(tname)
  }
}

// update the link -> url cache given our current model
// return new link table
func updateLinkCache() {
  // clear existing cache
  template.links = make(map[string]string)

  // for each group
  for _, group := range template.groups {
    // for each page in group
    for _, page := range group {
      // for each thread in page
      for _, thread := range page.Threads() {
        // put op entry
        h := thread.OP().ShortHash()
        u := thread.OP().PostURL()
        template.links[h] = u
        // for each reply
        for _, p := range thread.Replies() {
          // put reply entry
          h = p.ShortHash()
          u = p.PostURL()
          template.links[h] = u
        }
      }
    }
  }
}

// get the url for a backlink
func (self templateEngine) findLink(shorthash string) (url string) {
  url, _ = self.links[shorthash]
  return
}

// get the filepath to a template
func (self templateEngine) templateFilepath(name string) string {
  if strings.Count(name, "..") > 0 {
    return ""
  }
  return filepath.Join(self.template_dir, name)
}

// load a template from file, return as string
func (self templateEngine) loadTemplate(name string) (t string) {
  b, err := ioutil.ReadFile(self.templateFilepath(name))
  if err == nil {
    t = string(b)
  } else {
    log.Println("error loading template", err)
    t = err.Error()
  }
  return
}

// get a template, if it's not cached load from file and cache it
func (self templateEngine) getTemplate(name string) (t string) {
  if ! self.templateCached(name) {
    self.templates[name] = self.loadTemplate(name)
  }
  t, _ = self.templates[name]
  return
}

// render a template, self explanitory
func (self templateEngine) renderTemplate(name string, obj interface{}) string {
  t := self.getTemplate(name)
  return mustache.Render(t, obj)
}

// get a board model given a newsgroup
// load un updated board model if we don't have it
func (self templateEngine) obtainBoard(prefix, frontend, group string, db Database) (model GroupModel) {
  model, ok := self.groups[group]
  // if we don't already have the board loaded load it
  if ! ok  {
    p := db.GetGroupPageCount(group)
    pages := int(p)
    // ignore error
    perpage, _ := db.GetThreadsPerPage(group)
    for page := 0 ; page < pages ; page ++ {
      model = append(model, db.GetGroupForPage(prefix, frontend, group, page, int(perpage)))
    }
    self.groups[group] = model
  }
  return

}
// generate a board page
func (self templateEngine) genBoardPage(prefix, frontend, newsgroup string, page int, outfile string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the board page
  board = board.Update(page, db)
  if page >= len(board) {
    log.Println("board page should not exist", newsgroup, "page", page)
    return
  }
  // render it
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    board[page].RenderTo(wr)
    wr.Close()
    log.Println("wrote file", outfile)
  } else {
    log.Println("error generating board page", page, "for", newsgroup, err)
  }
  // save it
  self.groups[newsgroup] = board
}

// generate every page for a board
func (self templateEngine) genBoard(prefix, frontend, newsgroup, outdir string, db Database) {
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // update the entire board model
  board = board.UpdateAll(db)
  // save the model
  self.groups[newsgroup] = board
  updateLinkCache()

  pages := len(board)
  for page := 0 ; page < pages ; page ++ {
    outfile := filepath.Join(outdir, fmt.Sprintf("%s-%d.html", newsgroup, page))
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      board[page].RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("error generating board page", page, "for", newsgroup, err)
    }
  }
}

func (self templateEngine) genUkko(prefix, frontend, outfile string, database Database) {
  var threads []ThreadModel
  // get the last 15 bumped threads globally, for each...
  for _, article := range database.GetLastBumpedThreads("", 15) {
    // get the newsgroup and root post id
    newsgroup, msgid := article[1], article[0]
    // obtain board model
    board := self.obtainBoard(prefix, frontend, newsgroup, database)
    // update first page
    board = board.Update(0, database)
    // grab the root post in question
    th := board[0].GetThread(msgid)
    if th != nil {
        threads = append(threads, th.Update(database))
    }
    // save board model
    self.groups[newsgroup] = board
  }
  wr, err := OpenFileWriter(outfile)
  if err == nil {
    updateLinkCache()
    io.WriteString(wr, template.renderTemplate("ukko.mustache", map[string]interface{} { "prefix" : prefix, "threads" : threads }))
    wr.Close()
  } else {
    log.Println("error generating ukko", err)
  }
}

func (self templateEngine) genThread(root ArticleEntry, prefix, frontend, outfile string, db Database) {
  newsgroup := root.Newsgroup()
  msgid := root.MessageID()
  var th ThreadModel
  // get the board model
  board := self.obtainBoard(prefix, frontend, newsgroup, db)
  // find the thread model in question
  for _, pagemodel := range board {
    t := pagemodel.GetThread(msgid)
    if t != nil {
      th = t
      break
    }
  }

  if th == nil {
    // a new thread?
    board[0] = board[0].Update(db)
    t := board[0].GetThread(msgid)
    if t != nil {
      th = t
    }
  }

  if th == nil {
    log.Println("we didn't find thread for", msgid, "did not regenerate")
  } else {
    // update thread model and write it out
    th = th.Update(db)
    wr, err := OpenFileWriter(outfile)
    if err == nil {
      updateLinkCache()
      th.RenderTo(wr)
      wr.Close()
      log.Println("wrote file", outfile)
    } else {
      log.Println("did not write", outfile, err)
    }
  }
  // save it
  self.groups[newsgroup] = board
}

func newTemplateEngine(dir string) *templateEngine {
  return &templateEngine{
    groups: make(map[string]GroupModel),
    templates: make(map[string]string),
    template_dir: dir,
  }
}

var template = newTemplateEngine(defaultTemplateDir())

func renderPostForm(prefix, board, op_msg_id string) string {
  url := prefix + "post/" + board
  button := "New Thread"
  if op_msg_id != "" {
    button = "Reply"
  }
  return template.renderTemplate("postform.mustache", map[string]string { "post_url" : url, "reference" : op_msg_id , "button" : button } )
}

func (self templateEngine) genFrontPage(top_count int, frontend_name, outfile string, db Database) {
  // the graph for the front page
  var frontpage_graph boardPageRows

  // for each group
  groups := db.GetAllNewsgroups()
  for _, group := range groups {
    // posts per hour
    hour := db.CountPostsInGroup(group, 3600)
    // posts per day
    day := db.CountPostsInGroup(group, 86400)
    // posts total
    all := db.CountPostsInGroup(group, 0)
    frontpage_graph = append(frontpage_graph, boardPageRow{
      All: all,
      Day: day,
      Hour: hour,
      Board: group,
    })
  }
  wr, err := OpenFileWriter(outfile)
  if err != nil {
    log.Println("cannot render front page", err)
    return
  }

  param := make(map[string]interface{})
  sort.Sort(frontpage_graph)
  param["graph"] = frontpage_graph
  param["frontend"] = frontend_name
  param["totalposts"] = db.ArticleCount()
  _, err = io.WriteString(wr, self.renderTemplate("frontpage.mustache", param))
  if err != nil {
    log.Println("error writing front page", err)
  }
  wr.Close()
}
<|endoftext|>


<commit_before>package multiclusterapp

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/pkg/errors"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/parse"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	gaccess "github.com/rancher/rancher/pkg/api/customization/globalnamespaceaccess"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	managementschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
	"github.com/rancher/types/client/management/v3"

	"k8s.io/apimachinery/pkg/api/meta"
)

const (
	addProjectsAction    = "addProjects"
	removeProjectsAction = "removeProjects"
)

func (w Wrapper) Formatter(apiContext *types.APIContext, resource *types.RawResource) {
	resource.AddAction(apiContext, "rollback")
	resource.AddAction(apiContext, addProjectsAction)
	resource.AddAction(apiContext, removeProjectsAction)
	resource.Links["revisions"] = apiContext.URLBuilder.Link("revisions", resource)
}

func (w Wrapper) ActionHandler(actionName string, action *types.Action, apiContext *types.APIContext) error {
	var mcApp client.MultiClusterApp
	if err := access.ByID(apiContext, &managementschema.Version, client.MultiClusterAppType, apiContext.ID, &mcApp); err != nil {
		return err
	}
	switch actionName {
	case "rollback":
		data, err := ioutil.ReadAll(apiContext.Request.Body)
		if err != nil {
			return errors.Wrap(err, "reading request body error")
		}
		input := client.MultiClusterAppRollbackInput{}
		if err = json.Unmarshal(data, &input); err != nil {
			return errors.Wrap(err, "unmarshal input error")
		}
		id := input.RevisionID
		splitID := strings.Split(input.RevisionID, ":")
		if len(splitID) == 2 {
			id = splitID[1]
		}
		revision, err := w.MultiClusterAppRevisionLister.Get(namespace.GlobalNamespace, id)
		if err != nil {
			return err
		}
		obj, err := w.MultiClusterAppLister.Get(namespace.GlobalNamespace, mcApp.Name)
		if err != nil {
			return err
		}
		if obj.Status.RevisionName == revision.Name {
			return nil
		}
		toUpdate := obj.DeepCopy()
		toUpdate.Spec.TemplateVersionName = revision.TemplateVersionName
		toUpdate.Spec.Answers = revision.Answers
		_, err = w.MultiClusterApps.Update(toUpdate)
		return err
	case addProjectsAction:
		return w.addProjects(apiContext)
	case removeProjectsAction:
		return w.removeProjects(apiContext)
	default:
		return fmt.Errorf("bad action for multiclusterapp %v", actionName)
	}
}

func (w Wrapper) addProjects(request *types.APIContext) error {
	mcapp, existingProjects, inputProjects, inputAnswers, err := w.modifyProjects(request, addProjectsAction)
	if err != nil {
		return err
	}
	updatedMcApp := mcapp
	if len(inputAnswers) > 0 {
		mcappToUpdate := mcapp.DeepCopy()
		mcappToUpdate.Spec.Answers = append(mcappToUpdate.Spec.Answers, inputAnswers...)
		if updatedMcApp, err = w.MultiClusterApps.Update(mcappToUpdate); err != nil {
			return err
		}
	}
	mcappToUpdate := updatedMcApp.DeepCopy()
	for _, p := range inputProjects {
		if !existingProjects[p] {
			mcappToUpdate.Spec.Targets = append(mcappToUpdate.Spec.Targets, v3.Target{ProjectName: p})
		}
	}
	return w.updateMcApp(mcappToUpdate, request, "addedProjects")
}

func (w Wrapper) removeProjects(request *types.APIContext) error {
	mcapp, _, inputProjects, _, err := w.modifyProjects(request, removeProjectsAction)
	if err != nil {
		return err
	}
	mcappToUpdate := mcapp.DeepCopy()
	toRemoveProjects := make(map[string]bool)
	var finalTargets []v3.Target
	for _, p := range inputProjects {
		toRemoveProjects[p] = true
	}
	for _, t := range mcapp.Spec.Targets {
		if !toRemoveProjects[t.ProjectName] {
			finalTargets = append(finalTargets, t)
		}
	}
	mcappToUpdate.Spec.Targets = finalTargets
	return w.updateMcApp(mcappToUpdate, request, "removedProjects")
}

func (w Wrapper) modifyProjects(request *types.APIContext, actionName string) (*v3.MultiClusterApp, map[string]bool, []string, []v3.Answer, error) {
	split := strings.SplitN(request.ID, ":", 2)
	if len(split) != 2 {
		return nil, map[string]bool{}, []string{}, []v3.Answer{}, fmt.Errorf("incorrect multi cluster app ID %v", request.ID)
	}
	var inputProjects []string
	var inputAnswers []v3.Answer
	existingProjects := make(map[string]bool)
	mcapp, err := w.MultiClusterAppLister.Get(split[0], split[1])
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	// ensure that caller is not a readonly member of multiclusterapp, else abort
	callerID := request.Request.Header.Get(gaccess.ImpersonateUserHeader)
	metaAccessor, err := meta.Accessor(mcapp)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	creatorID, ok := metaAccessor.GetAnnotations()[creatorIDAnn]
	if !ok {
		return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("multiclusterapp %v has no creatorId annotation", metaAccessor.GetName())
	}
	ma := gaccess.MemberAccess{
		Users:      w.Users,
		PrtbLister: w.PrtbLister,
	}
	accessType, err := ma.GetAccessTypeOfCaller(callerID, creatorID, mcapp.Name, mcapp.Spec.Members)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	if accessType != gaccess.OwnerAccess {
		return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("only owners can modify projects of multiclusterapp")
	}
	var updateMultiClusterAppTargetsInput v3.UpdateMultiClusterAppTargetsInput
	actionInput, err := parse.ReadBody(request.Request)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	if err = convert.ToObj(actionInput, &updateMultiClusterAppTargetsInput); err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	inputProjects = updateMultiClusterAppTargetsInput.Projects
	for _, p := range mcapp.Spec.Targets {
		existingProjects[p.ProjectName] = true
	}
	if actionName == addProjectsAction {
		if err = ma.EnsureRoleInTargets(inputProjects, mcapp.Spec.Roles, callerID); err != nil {
			return nil, existingProjects, inputProjects, inputAnswers, err
		}
	}
	inputAnswers = updateMultiClusterAppTargetsInput.Answers
	// check if the input includes answers, and if they are only for the input projects
	if len(inputAnswers) > 0 {
		inputProjectsMap := make(map[string]bool)
		for _, p := range inputProjects {
			if !inputProjectsMap[p] {
				inputProjectsMap[p] = true
			}
		}
		for _, a := range inputAnswers {
			if a.ProjectName == "" {
				return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("can only provide project-scoped answers for new projects through add/remove projects action")
			}
			if !inputProjectsMap[a.ProjectName] {
				return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("the project %v is not among the ones provided in input", a.ProjectName)
			}
		}
	}
	return mcapp, existingProjects, inputProjects, inputAnswers, nil
}

func (w Wrapper) updateMcApp(mcappToUpdate *v3.MultiClusterApp, request *types.APIContext, message string) error {
	if _, err := w.MultiClusterApps.Update(mcappToUpdate); err != nil {
		return err
	}

	op := map[string]interface{}{
		"message": message,
	}
	request.WriteResponse(http.StatusOK, op)
	return nil
}
<commit_msg>Fix panic on addProjects while checking access to targets<commit_after>package multiclusterapp

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"

	"github.com/pkg/errors"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/parse"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	gaccess "github.com/rancher/rancher/pkg/api/customization/globalnamespaceaccess"
	"github.com/rancher/rancher/pkg/namespace"
	"github.com/rancher/types/apis/management.cattle.io/v3"
	managementschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
	"github.com/rancher/types/client/management/v3"

	"k8s.io/apimachinery/pkg/api/meta"
)

const (
	addProjectsAction    = "addProjects"
	removeProjectsAction = "removeProjects"
)

func (w Wrapper) Formatter(apiContext *types.APIContext, resource *types.RawResource) {
	resource.AddAction(apiContext, "rollback")
	resource.AddAction(apiContext, addProjectsAction)
	resource.AddAction(apiContext, removeProjectsAction)
	resource.Links["revisions"] = apiContext.URLBuilder.Link("revisions", resource)
}

func (w Wrapper) ActionHandler(actionName string, action *types.Action, apiContext *types.APIContext) error {
	var mcApp client.MultiClusterApp
	if err := access.ByID(apiContext, &managementschema.Version, client.MultiClusterAppType, apiContext.ID, &mcApp); err != nil {
		return err
	}
	switch actionName {
	case "rollback":
		data, err := ioutil.ReadAll(apiContext.Request.Body)
		if err != nil {
			return errors.Wrap(err, "reading request body error")
		}
		input := client.MultiClusterAppRollbackInput{}
		if err = json.Unmarshal(data, &input); err != nil {
			return errors.Wrap(err, "unmarshal input error")
		}
		id := input.RevisionID
		splitID := strings.Split(input.RevisionID, ":")
		if len(splitID) == 2 {
			id = splitID[1]
		}
		revision, err := w.MultiClusterAppRevisionLister.Get(namespace.GlobalNamespace, id)
		if err != nil {
			return err
		}
		obj, err := w.MultiClusterAppLister.Get(namespace.GlobalNamespace, mcApp.Name)
		if err != nil {
			return err
		}
		if obj.Status.RevisionName == revision.Name {
			return nil
		}
		toUpdate := obj.DeepCopy()
		toUpdate.Spec.TemplateVersionName = revision.TemplateVersionName
		toUpdate.Spec.Answers = revision.Answers
		_, err = w.MultiClusterApps.Update(toUpdate)
		return err
	case addProjectsAction:
		return w.addProjects(apiContext)
	case removeProjectsAction:
		return w.removeProjects(apiContext)
	default:
		return fmt.Errorf("bad action for multiclusterapp %v", actionName)
	}
}

func (w Wrapper) addProjects(request *types.APIContext) error {
	mcapp, existingProjects, inputProjects, inputAnswers, err := w.modifyProjects(request, addProjectsAction)
	if err != nil {
		return err
	}
	updatedMcApp := mcapp
	if len(inputAnswers) > 0 {
		mcappToUpdate := mcapp.DeepCopy()
		mcappToUpdate.Spec.Answers = append(mcappToUpdate.Spec.Answers, inputAnswers...)
		if updatedMcApp, err = w.MultiClusterApps.Update(mcappToUpdate); err != nil {
			return err
		}
	}
	mcappToUpdate := updatedMcApp.DeepCopy()
	for _, p := range inputProjects {
		if !existingProjects[p] {
			mcappToUpdate.Spec.Targets = append(mcappToUpdate.Spec.Targets, v3.Target{ProjectName: p})
		}
	}
	return w.updateMcApp(mcappToUpdate, request, "addedProjects")
}

func (w Wrapper) removeProjects(request *types.APIContext) error {
	mcapp, _, inputProjects, _, err := w.modifyProjects(request, removeProjectsAction)
	if err != nil {
		return err
	}
	mcappToUpdate := mcapp.DeepCopy()
	toRemoveProjects := make(map[string]bool)
	var finalTargets []v3.Target
	for _, p := range inputProjects {
		toRemoveProjects[p] = true
	}
	for _, t := range mcapp.Spec.Targets {
		if !toRemoveProjects[t.ProjectName] {
			finalTargets = append(finalTargets, t)
		}
	}
	mcappToUpdate.Spec.Targets = finalTargets
	return w.updateMcApp(mcappToUpdate, request, "removedProjects")
}

func (w Wrapper) modifyProjects(request *types.APIContext, actionName string) (*v3.MultiClusterApp, map[string]bool, []string, []v3.Answer, error) {
	split := strings.SplitN(request.ID, ":", 2)
	if len(split) != 2 {
		return nil, map[string]bool{}, []string{}, []v3.Answer{}, fmt.Errorf("incorrect multi cluster app ID %v", request.ID)
	}
	var inputProjects []string
	var inputAnswers []v3.Answer
	existingProjects := make(map[string]bool)
	mcapp, err := w.MultiClusterAppLister.Get(split[0], split[1])
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	// ensure that caller is not a readonly member of multiclusterapp, else abort
	callerID := request.Request.Header.Get(gaccess.ImpersonateUserHeader)
	metaAccessor, err := meta.Accessor(mcapp)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	creatorID, ok := metaAccessor.GetAnnotations()[creatorIDAnn]
	if !ok {
		return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("multiclusterapp %v has no creatorId annotation", metaAccessor.GetName())
	}
	ma := gaccess.MemberAccess{
		Users:              w.Users,
		PrtbLister:         w.PrtbLister,
		CrtbLister:         w.CrtbLister,
		RoleTemplateLister: w.RoleTemplateLister,
	}
	accessType, err := ma.GetAccessTypeOfCaller(callerID, creatorID, mcapp.Name, mcapp.Spec.Members)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	if accessType != gaccess.OwnerAccess {
		return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("only owners can modify projects of multiclusterapp")
	}
	var updateMultiClusterAppTargetsInput v3.UpdateMultiClusterAppTargetsInput
	actionInput, err := parse.ReadBody(request.Request)
	if err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	if err = convert.ToObj(actionInput, &updateMultiClusterAppTargetsInput); err != nil {
		return nil, existingProjects, inputProjects, inputAnswers, err
	}
	inputProjects = updateMultiClusterAppTargetsInput.Projects
	for _, p := range mcapp.Spec.Targets {
		existingProjects[p.ProjectName] = true
	}
	if actionName == addProjectsAction {
		if err = ma.EnsureRoleInTargets(inputProjects, mcapp.Spec.Roles, callerID); err != nil {
			return nil, existingProjects, inputProjects, inputAnswers, err
		}
	}
	inputAnswers = updateMultiClusterAppTargetsInput.Answers
	// check if the input includes answers, and if they are only for the input projects
	if len(inputAnswers) > 0 {
		inputProjectsMap := make(map[string]bool)
		for _, p := range inputProjects {
			if !inputProjectsMap[p] {
				inputProjectsMap[p] = true
			}
		}
		for _, a := range inputAnswers {
			if a.ProjectName == "" {
				return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("can only provide project-scoped answers for new projects through add/remove projects action")
			}
			if !inputProjectsMap[a.ProjectName] {
				return nil, existingProjects, inputProjects, inputAnswers, fmt.Errorf("the project %v is not among the ones provided in input", a.ProjectName)
			}
		}
	}
	return mcapp, existingProjects, inputProjects, inputAnswers, nil
}

func (w Wrapper) updateMcApp(mcappToUpdate *v3.MultiClusterApp, request *types.APIContext, message string) error {
	if _, err := w.MultiClusterApps.Update(mcappToUpdate); err != nil {
		return err
	}

	op := map[string]interface{}{
		"message": message,
	}
	request.WriteResponse(http.StatusOK, op)
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 openstack

import (
	"errors"
	"time"
	"fmt"
	"io/ioutil"
	"path"
	"strings"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/golang/glog"
)

// Attaches given cinder volume to the compute running kubelet
func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return "", err
	}
	cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})
	if err != nil || cClient == nil {
		glog.Errorf("Unable to initialize nova client for region: %s", os.region)
		return "", err
	}

	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
		if instanceID == disk.Attachments[0]["server_id"] {
			glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID)
			return disk.ID, nil
		} else {
			errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding. DETACHING DIS SHIT Y0!!", diskName, disk.Attachments[0]["server_id"])
			glog.Errorf(errMsg)
			err = os.DetachDisk(fmt.Sprintf("%s", disk.Attachments[0]["server_id"]), diskName)
			glog.Errorf(err.Error())
			time.Sleep(15000 * time.Millisecond)
		}
	}
	// add read only flag here if possible spothanis
	_, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{
		VolumeID: disk.ID,
	}).Extract()
	if err != nil {
		glog.Errorf("Failed to attach %s volume to %s compute", diskName, instanceID)
		return "", err
	}
	glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID)
	return disk.ID, nil
}

// Detaches given cinder volume from the compute running kubelet
func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error {
	disk, err := os.getVolume(partialDiskId)
	if err != nil {
		return err
	}
	cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})
	if err != nil || cClient == nil {
		glog.Errorf("Unable to initialize nova client for region: %s", os.region)
		return err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
		// This is a blocking call and effects kubelet's performance directly.
		// We should consider kicking it out into a separate routine, if it is bad.
		err = volumeattach.Delete(cClient, instanceID, disk.ID).ExtractErr()
		if err != nil {
			glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, instanceID, err)
			return err
		}
		glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, instanceID)
	} else {
		errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, instanceID)
		glog.Errorf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

// Takes a partial/full disk id or diskname
func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) {
	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	var volume volumes.Volume
	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return volume, err
	}

	err = volumes.List(sClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		vols, err := volumes.ExtractVolumes(page)
		if err != nil {
			glog.Errorf("Failed to extract volumes: %v", err)
			return false, err
		} else {
			for _, v := range vols {
				glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
				if v.Name == diskName || strings.Contains(v.ID, diskName) {
					volume = v
					return true, nil
				}
			}
		}
		// if it reached here then no disk with the given name was found.
		errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, os.region)
		return false, errors.New(errmsg)
	})
	if err != nil {
		glog.Errorf("Error occurred getting volume: %s", diskName)
		return volume, err
	}
	return volume, err
}

// Create a volume of given size (in GiB)
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (volumeName string, err error) {

	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return "", err
	}

	opts := volumes.CreateOpts{
		Name:         name,
		Size:         size,
		VolumeType:   vtype,
		Availability: availability,
	}
	if tags != nil {
		opts.Metadata = *tags
	}
	vol, err := volumes.Create(sClient, opts).Extract()
	if err != nil {
		glog.Errorf("Failed to create a %d GB volume: %v", size, err)
		return "", err
	}
	glog.Infof("Created volume %v", vol.ID)
	return vol.ID, err
}

// GetDevicePath returns the path of an attached block storage volume, specified by its id.
func (os *OpenStack) GetDevicePath(diskId string) string {
	files, _ := ioutil.ReadDir("/dev/disk/by-id/")
	for _, f := range files {
		if strings.Contains(f.Name(), "virtio-") {
			devid_prefix := f.Name()[len("virtio-"):len(f.Name())]
			if strings.Contains(diskId, devid_prefix) {
				glog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name()))
				return path.Join("/dev/disk/by-id/", f.Name())
			}
		}
	}
	glog.Warningf("Failed to find device for the diskid: %q\n", diskId)
	return ""
}

func (os *OpenStack) DeleteVolume(volumeName string) error {
	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return err
	}
	err = volumes.Delete(sClient, volumeName).ExtractErr()
	if err != nil {
		glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
	}
	return err
}

// Get device path of attached volume to the compute running kubelet
func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return "", err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
		if instanceID == disk.Attachments[0]["server_id"] {
			// Attachment[0]["device"] points to the device path
			// see http://developer.openstack.org/api-ref-blockstorage-v1.html
			return disk.Attachments[0]["device"].(string), nil
		} else {
			errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
			glog.Errorf(errMsg)
			return "", errors.New(errMsg)
		}
	}
	return "", fmt.Errorf("volume %s is not attached to %s", diskName, instanceID)
}

// query if a volume is attached to a compute instance
func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return false, err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
		return true, nil
	}
	return false, nil
}
<commit_msg>FOO BAR CRAP<commit_after>/*
Copyright 2016 The Kubernetes 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 openstack

import (
	"errors"
	"time"
	"fmt"
	"io/ioutil"
	"path"
	"strings"

	"github.com/rackspace/gophercloud"
	"github.com/rackspace/gophercloud/openstack"
	"github.com/rackspace/gophercloud/openstack/blockstorage/v1/volumes"
	"github.com/rackspace/gophercloud/openstack/compute/v2/extensions/volumeattach"
	"github.com/rackspace/gophercloud/pagination"

	"github.com/golang/glog"
)

// Attaches given cinder volume to the compute running kubelet
func (os *OpenStack) AttachDisk(instanceID string, diskName string) (string, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return "", err
	}
	cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})
	if err != nil || cClient == nil {
		glog.Errorf("Unable to initialize nova client for region: %s", os.region)
		return "", err
	}

	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
		if instanceID == disk.Attachments[0]["server_id"] {
			glog.V(4).Infof("Disk: %q is already attached to compute: %q", diskName, instanceID)
			return disk.ID, nil
		} else {
			errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, detaching", diskName, disk.Attachments[0]["server_id"])
			glog.Errorf(errMsg)
			err = os.DetachDisk(fmt.Sprintf("%s", disk.Attachments[0]["server_id"]), diskName)
			if err != nil {
				glog.Errorf(err.Error())
			}
		}
	}
	// add read only flag here if possible spothanis
	_, err = volumeattach.Create(cClient, instanceID, &volumeattach.CreateOpts{
		VolumeID: disk.ID,
	}).Extract()
	if err != nil {
		glog.Errorf("Failed to attach %s volume to %s compute", diskName, instanceID)
		return "", err
	}
	glog.V(2).Infof("Successfully attached %s volume to %s compute", diskName, instanceID)
	return disk.ID, nil
}

// Detaches given cinder volume from the compute running kubelet
func (os *OpenStack) DetachDisk(instanceID string, partialDiskId string) error {
	disk, err := os.getVolume(partialDiskId)
	if err != nil {
		return err
	}
	cClient, err := openstack.NewComputeV2(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})
	if err != nil || cClient == nil {
		glog.Errorf("Unable to initialize nova client for region: %s", os.region)
		return err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
		// This is a blocking call and effects kubelet's performance directly.
		// We should consider kicking it out into a separate routine, if it is bad.
		err = volumeattach.Delete(cClient, instanceID, disk.ID).ExtractErr()
		if err != nil {
			glog.Errorf("Failed to delete volume %s from compute %s attached %v", disk.ID, instanceID, err)
			return err
		}
		glog.V(2).Infof("Successfully detached volume: %s from compute: %s", disk.ID, instanceID)
	} else {
		errMsg := fmt.Sprintf("Disk: %s has no attachments or is not attached to compute: %s", disk.Name, instanceID)
		glog.Errorf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

// Takes a partial/full disk id or diskname
func (os *OpenStack) getVolume(diskName string) (volumes.Volume, error) {
	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	var volume volumes.Volume
	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return volume, err
	}

	err = volumes.List(sClient, nil).EachPage(func(page pagination.Page) (bool, error) {
		vols, err := volumes.ExtractVolumes(page)
		if err != nil {
			glog.Errorf("Failed to extract volumes: %v", err)
			return false, err
		} else {
			for _, v := range vols {
				glog.V(4).Infof("%s %s %v", v.ID, v.Name, v.Attachments)
				if v.Name == diskName || strings.Contains(v.ID, diskName) {
					volume = v
					return true, nil
				}
			}
		}
		// if it reached here then no disk with the given name was found.
		errmsg := fmt.Sprintf("Unable to find disk: %s in region %s", diskName, os.region)
		return false, errors.New(errmsg)
	})
	if err != nil {
		glog.Errorf("Error occurred getting volume: %s", diskName)
		return volume, err
	}
	return volume, err
}

// Create a volume of given size (in GiB)
func (os *OpenStack) CreateVolume(name string, size int, vtype, availability string, tags *map[string]string) (volumeName string, err error) {

	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return "", err
	}

	opts := volumes.CreateOpts{
		Name:         name,
		Size:         size,
		VolumeType:   vtype,
		Availability: availability,
	}
	if tags != nil {
		opts.Metadata = *tags
	}
	vol, err := volumes.Create(sClient, opts).Extract()
	if err != nil {
		glog.Errorf("Failed to create a %d GB volume: %v", size, err)
		return "", err
	}
	glog.Infof("Created volume %v", vol.ID)
	return vol.ID, err
}

// GetDevicePath returns the path of an attached block storage volume, specified by its id.
func (os *OpenStack) GetDevicePath(diskId string) string {
	files, _ := ioutil.ReadDir("/dev/disk/by-id/")
	for _, f := range files {
		if strings.Contains(f.Name(), "virtio-") {
			devid_prefix := f.Name()[len("virtio-"):len(f.Name())]
			if strings.Contains(diskId, devid_prefix) {
				glog.V(4).Infof("Found disk attached as %q; full devicepath: %s\n", f.Name(), path.Join("/dev/disk/by-id/", f.Name()))
				return path.Join("/dev/disk/by-id/", f.Name())
			}
		}
	}
	glog.Warningf("Failed to find device for the diskid: %q\n", diskId)
	return ""
}

func (os *OpenStack) DeleteVolume(volumeName string) error {
	sClient, err := openstack.NewBlockStorageV1(os.provider, gophercloud.EndpointOpts{
		Region: os.region,
	})

	if err != nil || sClient == nil {
		glog.Errorf("Unable to initialize cinder client for region: %s", os.region)
		return err
	}
	err = volumes.Delete(sClient, volumeName).ExtractErr()
	if err != nil {
		glog.Errorf("Cannot delete volume %s: %v", volumeName, err)
	}
	return err
}

// Get device path of attached volume to the compute running kubelet
func (os *OpenStack) GetAttachmentDiskPath(instanceID string, diskName string) (string, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return "", err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil {
		if instanceID == disk.Attachments[0]["server_id"] {
			// Attachment[0]["device"] points to the device path
			// see http://developer.openstack.org/api-ref-blockstorage-v1.html
			return disk.Attachments[0]["device"].(string), nil
		} else {
			errMsg := fmt.Sprintf("Disk %q is attached to a different compute: %q, should be detached before proceeding", diskName, disk.Attachments[0]["server_id"])
			glog.Errorf(errMsg)
			return "", errors.New(errMsg)
		}
	}
	return "", fmt.Errorf("volume %s is not attached to %s", diskName, instanceID)
}

// query if a volume is attached to a compute instance
func (os *OpenStack) DiskIsAttached(diskName, instanceID string) (bool, error) {
	disk, err := os.getVolume(diskName)
	if err != nil {
		return false, err
	}
	if len(disk.Attachments) > 0 && disk.Attachments[0]["server_id"] != nil && instanceID == disk.Attachments[0]["server_id"] {
		return true, nil
	}
	return false, nil
}
<|endoftext|>


<commit_before>package static

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"path"
	"path/filepath"
)

func exists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

// Static returns a middleware handler that serves static files in the given directory.
func Serve(directories ...interface{}) gin.HandlerFunc {
	fileservers := []http.Handler{}

	for i := 0; i < len(directories); i++ {
		directory, err := filepath.Abs(directories[i].(string))
		if err != nil {
			panic(err)
		}
		fileservers = append(fileservers, http.FileServer(http.Dir(directory)))
	}

	return func(c *gin.Context) {
		for i := 0; i < len(directories); i++ {
			directory := directories[i].(string)
			p := path.Join(directory, c.Request.URL.Path)
			if exists(p) {
				fileservers[i].ServeHTTP(c.Writer, c.Request)
				c.Abort(-1)
				break
			}
		}
	}
}
<commit_msg>Delete argument "-1" from Abort()<commit_after>package static

import (
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"path"
	"path/filepath"
)

func exists(name string) bool {
	_, err := os.Stat(name)
	return !os.IsNotExist(err)
}

// Static returns a middleware handler that serves static files in the given directory.
func Serve(directories ...interface{}) gin.HandlerFunc {
	fileservers := []http.Handler{}

	for i := 0; i < len(directories); i++ {
		directory, err := filepath.Abs(directories[i].(string))
		if err != nil {
			panic(err)
		}
		fileservers = append(fileservers, http.FileServer(http.Dir(directory)))
	}

	return func(c *gin.Context) {
		for i := 0; i < len(directories); i++ {
			directory := directories[i].(string)
			p := path.Join(directory, c.Request.URL.Path)
			if exists(p) {
				fileservers[i].ServeHTTP(c.Writer, c.Request)
				c.Abort()
				break
			}
		}
	}
}
<|endoftext|>


<commit_before>package steps

import (
	"time"

	projectapi "github.com/openshift/origin/pkg/project/api"

	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
)

// registers all project related steps
func init() {
	RegisterSteps(func(c *Context) {

		c.Given(`^There is no project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if found {
				if err = c.DeleteProject(projectName); err != nil {
					c.Fail("Failed to delete project '%s': %v", projectName, err)
					return
				}

				stillPresent, err := c.ProjectExists(projectName)
				if err != nil {
					c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
					return
				}

				if stillPresent {
					c.Fail("Failed to delete existing project %s", projectName)
					return
				}
			}
		})

		c.Given(`^I have an existing project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				if err := c.CreateNewProject(projectName); err != nil {
					c.Fail("Failed to create new project %s: %v", projectName, err)
					return
				}
			}
		})

		c.Given(`^My current project is "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				c.Fail("Project '%s' does not exists", projectName)
				return
			}

			c.setNamespace(projectName)
		})

		c.When(`^I create a new project "(.+?)"$`, func(projectName string) {
			if err := c.CreateNewProject(projectName); err != nil {
				c.Fail("Failed to create new project %s: %v", projectName, err)
			}
		})

		c.When(`^I delete the project "(.+?)"$`, func(projectName string) {
			if err := c.DeleteProject(projectName); err != nil {
				c.Fail("Failed to delete project %s: %v", projectName, err)
			}
		})

		c.Then(`^I should have a project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				c.Fail("Failed to find a project named %s", projectName)
				return
			}
		})

		c.Then(`^I should not have a project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if found {
				c.Fail("Project %s should not exists", projectName)
				return
			}
		})

	})
}

// ProjectExists checks if a project with the given name exists.
func (c *Context) ProjectExists(projectName string) (bool, error) {
	client, _, err := c.Clients()
	if err != nil {
		return false, err
	}

	projectList, err := client.Projects().List(labels.Everything(), fields.Everything())
	if err != nil {
		return false, err
	}

	for _, p := range projectList.Items {
		if p.Name == projectName {
			return true, nil
		}
	}
	return false, nil
}

// DeleteProject deletes the project with the given name, or returns an error
func (c *Context) DeleteProject(projectName string) error {
	client, _, err := c.Clients()
	if err != nil {
		return err
	}

	if err = client.Projects().Delete(projectName); err != nil {
		return err
	}

	// make sure the project has been deleted
	for {
		exists, err := c.ProjectExists(projectName)
		if err != nil {
			return err
		}
		if !exists {
			break
		}
		time.Sleep(1 * time.Second)
	}

	return nil
}

// CreateNewProject creates a new project with the given name, or returns an error
func (c *Context) CreateNewProject(projectName string) error {
	client, _, err := c.Clients()
	if err != nil {
		return err
	}

	projectRequest := &projectapi.ProjectRequest{}
	projectRequest.Name = projectName
	if _, err = client.ProjectRequests().Create(projectRequest); err != nil {
		return err
	}

	// make sure the project has been created
	for {
		exists, err := c.ProjectExists(projectName)
		if err != nil {
			return err
		}
		if exists {
			break
		}
		time.Sleep(1 * time.Second)
	}

	return nil
}
<commit_msg>retry with exp backoff<commit_after>package steps

import (
	"time"

	projectapi "github.com/openshift/origin/pkg/project/api"

	"k8s.io/kubernetes/pkg/fields"
	"k8s.io/kubernetes/pkg/labels"
)

// registers all project related steps
func init() {
	RegisterSteps(func(c *Context) {

		c.Given(`^There is no project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if found {
				if err = c.DeleteProject(projectName); err != nil {
					c.Fail("Failed to delete project '%s': %v", projectName, err)
					return
				}

				stillPresent, err := c.ProjectExists(projectName)
				if err != nil {
					c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
					return
				}

				if stillPresent {
					c.Fail("Failed to delete existing project %s", projectName)
					return
				}
			}
		})

		c.Given(`^I have an existing project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				if err := c.CreateNewProject(projectName); err != nil {
					c.Fail("Failed to create new project %s: %v", projectName, err)
					return
				}
			}
		})

		c.Given(`^My current project is "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				c.Fail("Project '%s' does not exists", projectName)
				return
			}

			c.setNamespace(projectName)
		})

		c.When(`^I create a new project "(.+?)"$`, func(projectName string) {
			if err := c.CreateNewProject(projectName); err != nil {
				c.Fail("Failed to create new project %s: %v", projectName, err)
			}
		})

		c.When(`^I delete the project "(.+?)"$`, func(projectName string) {
			if err := c.DeleteProject(projectName); err != nil {
				c.Fail("Failed to delete project %s: %v", projectName, err)
			}
		})

		c.Then(`^I should have a project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if !found {
				c.Fail("Failed to find a project named %s", projectName)
				return
			}
		})

		c.Then(`^I should not have a project "(.+?)"$`, func(projectName string) {
			found, err := c.ProjectExists(projectName)
			if err != nil {
				c.Fail("Failed to check for project '%s' existance: %v", projectName, err)
				return
			}

			if found {
				c.Fail("Project %s should not exists", projectName)
				return
			}
		})

	})
}

// ProjectExists checks if a project with the given name exists.
func (c *Context) ProjectExists(projectName string) (bool, error) {
	client, _, err := c.Clients()
	if err != nil {
		return false, err
	}

	projectList, err := client.Projects().List(labels.Everything(), fields.Everything())
	if err != nil {
		return false, err
	}

	for _, p := range projectList.Items {
		if p.Name == projectName {
			return true, nil
		}
	}
	return false, nil
}

// DeleteProject deletes the project with the given name, or returns an error
func (c *Context) DeleteProject(projectName string) error {
	client, _, err := c.Clients()
	if err != nil {
		return err
	}

	if err = client.Projects().Delete(projectName); err != nil {
		return err
	}

	// make sure the project has been deleted
	for {
		exists, err := c.ProjectExists(projectName)
		if err != nil {
			return err
		}
		if !exists {
			break
		}
		time.Sleep(1 * time.Second)
	}

	return nil
}

// CreateNewProject creates a new project with the given name, or returns an error
func (c *Context) CreateNewProject(projectName string) error {
	client, _, err := c.Clients()
	if err != nil {
		return err
	}

	projectRequest := &projectapi.ProjectRequest{}
	projectRequest.Name = projectName

	err = c.ExecWithExponentialBackoff(func() (err error) {
		_, err = client.ProjectRequests().Create(projectRequest)
		return
	})
	if err != nil {
		return err
	}

	// make sure the project has been created
	for {
		exists, err := c.ProjectExists(projectName)
		if err != nil {
			return err
		}
		if exists {
			break
		}
		time.Sleep(1 * time.Second)
	}

	return nil
}
<|endoftext|>


<commit_before>package rpc

import (
	_ "github.com/yak-labs/chirp-lang/goapi/default"

	"github.com/yak-labs/chirp-lang"
	"testing"
)

var rpcTests = `
    # This RPC will return 3 words: "extra_word", the verb, and the input hash:
	rpc serve /just_list "list extra_word"

    # Serve on port 31234, and wait 0.3 sec for it to start.
	go {/net/http/ListenAndServe localhost:31234 ""}
	/time/Sleep [expr {300 * [/time/Millisecond]}]

    # Call the RPC, storing resulting 3 words in w,v,h.
	set r [rpc connect http://localhost:31234/just_list]
	set w,v,h [rpc call $r SomeVerb [hash abc 123 xyz 789]]
	rpc close $r

    # Check $w and $v.
	must "extra_word" $w
	must "SomeVerb" $v

    # Check the hash.
	set h2 [hash $h]
	must "abc xyz" [hkeys $h2]
	must "123" [hget $h2 abc]
	must "789" [hget $h2 xyz]
`

func TestRpc(a *testing.T) {
	// chirp.Debug['a'] = true
	// chirp.Debug['e'] = true
	// chirp.Debug['w'] = true
	fr := chirp.NewInterpreter()
	fr.Eval(chirp.MkString(rpcTests))
}
<commit_msg>add multiply rpc test<commit_after>package rpc

import (
	_ "github.com/yak-labs/chirp-lang/goapi/default"

	"github.com/yak-labs/chirp-lang"
	"testing"
)

var rpcTests = `
    proc mult {verb h} { expr {$h(x) * $h(y)} }
    # This RPC will return 3 words: "extra_word", the verb, and the input hash:
	rpc serve /just_list "list extra_word"
	rpc serve /multiply "mult"

    # Serve on port 31234, and wait 0.3 sec for it to start.
	go {/net/http/ListenAndServe localhost:31234 ""}
	/time/Sleep [expr {300 * [/time/Millisecond]}]

    # Call the RPC, storing resulting 3 words in w,v,h.
	set r [rpc connect http://localhost:31234/just_list]
	set w,v,h [rpc call $r SomeVerb [hash abc 123 xyz 789]]
	rpc close $r

    # Check $w and $v.
	must "extra_word" $w
	must "SomeVerb" $v

    # Check the hash.
	set h2 [hash $h]
	must "abc xyz" [hkeys $h2]
	must "123" [hget $h2 abc]
	must "789" [hget $h2 xyz]

	set r2 [rpc connect http://localhost:31234/multiply]
	must 42 [rpc call $r2 DontCareAboutVerb [hash x 6 y 7]]
`

func TestRpc(a *testing.T) {
	// chirp.Debug['a'] = true
	// chirp.Debug['e'] = true
	// chirp.Debug['w'] = true
	fr := chirp.NewInterpreter()
	fr.Eval(chirp.MkString(rpcTests))
}
<|endoftext|>


<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// +build !windows

package libkbfs

import (
	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

// getDiskLimits gets the disk limits for the logical disk containing
// the given path.
func getDiskLimits(path string) (
	availableBytes, availableFiles uint64, err error) {
	var stat unix.Statfs_t
	err = unix.Statfs(path, &stat)
	if err != nil {
		return 0, 0, errors.WithStack(err)
	}

	// Bavail is the free block count for an unprivileged user.
	availableBytes = stat.Bavail * uint64(stat.Bsize)
	availableFiles = stat.Ffree
	return availableBytes, availableFiles, nil
}
<commit_msg>Detect FSs that don't keep track of inodes (#842)<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

// +build !windows

package libkbfs

import (
	"math"

	"github.com/pkg/errors"
	"golang.org/x/sys/unix"
)

// getDiskLimits gets the disk limits for the logical disk containing
// the given path.
func getDiskLimits(path string) (
	availableBytes, availableFiles uint64, err error) {
	var stat unix.Statfs_t
	err = unix.Statfs(path, &stat)
	if err != nil {
		return 0, 0, errors.WithStack(err)
	}

	// Bavail is the free block count for an unprivileged user.
	availableBytes = stat.Bavail * uint64(stat.Bsize)
	// Some filesystems, like btrfs, don't keep track of inodes.
	// (See https://github.com/keybase/client/issues/6206 .) Use
	// the total inode count to detect that case.
	if stat.Files > 0 {
		availableFiles = stat.Ffree
	} else {
		availableFiles = math.MaxInt64
	}
	return availableBytes, availableFiles, nil
}
<|endoftext|>


<commit_before>package stream

import (
	"sync"
)

type signal struct{}

var yes signal

//MergeUniqueStrings returns a channel which streams the unique output of
//all of the strings in the input channel. The order of output is not guaranteed.
func MergeUniqueStrings(chans ...chan string) chan string {
	uniques := make(chan string)
	wg := &sync.WaitGroup{}
	for _, ch := range chans {
		wg.Add(1)
		seen := make(map[string]signal)
		go func(ch <-chan string) {
			defer wg.Done()
			for str := range ch {
				if _, ok := seen[str]; !ok {
					seen[str] = yes
					uniques <- str
				}
			}
		}(ch)
	}
	go func() {
		wg.Wait()
		defer close(uniques)
	}()

	return uniques
}

//MergeStrings returns a channel which merges the input of all of the strings in it's input.
//The order of output is not guaranteed.
func MergeStrings(chans ...<-chan string) chan string {
	out := make(chan string)
	wg := &sync.WaitGroup{}
	for _, ch := range chans {
		wg.Add(1)
		go func(ch <-chan string) {
			defer wg.Done()
			for str := range ch {
				out <- str
			}
		}(ch)
	}
	go func() {
		wg.Wait()
		defer close(out)
	}()
	return out
}
<commit_msg>removed stream<commit_after><|endoftext|>


<commit_before>// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv

type Celsius float64
type Fahrenheit float64

const (
    AbsoluteZeroC   Celsius = -273.15
    FreezingC       Celsius = 0
    BoilingC        Celsius = 100
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c * 9 / 5 + 32)}

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9)}<commit_msg>added string method to celsius type<commit_after>// Package tempconv performs Celsius and Fahrenheit temperature computations.
package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64

const (
    AbsoluteZeroC   Celsius = -273.15
    FreezingC       Celsius = 0
    BoilingC        Celsius = 100
)

func CToF(c Celsius) Fahrenheit { return Fahrenheit(c * 9 / 5 + 32) }

func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }

func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }<|endoftext|>


<commit_before>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package jwalterweatherman

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
)

// Level describes the chosen log level between
// debug and critical.
type Level int

type NotePad struct {
	Handle io.Writer
	Level  Level
	Prefix string
	Logger **log.Logger
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
type Feedback struct{}

const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelCritical
	LevelFatal
	DefaultLogThreshold    = LevelWarn
	DefaultStdoutThreshold = LevelError
)

var (
	TRACE      *log.Logger
	DEBUG      *log.Logger
	INFO       *log.Logger
	WARN       *log.Logger
	ERROR      *log.Logger
	CRITICAL   *log.Logger
	FATAL      *log.Logger
	LOG        *log.Logger
	FEEDBACK   Feedback
	LogHandle  io.Writer  = ioutil.Discard
	OutHandle  io.Writer  = os.Stdout
	BothHandle io.Writer  = io.MultiWriter(LogHandle, OutHandle)
	NotePads   []*NotePad = []*NotePad{trace, debug, info, warn, err, critical, fatal}

	trace           *NotePad = &NotePad{Level: LevelTrace, Handle: os.Stdout, Logger: &TRACE, Prefix: "TRACE: "}
	debug           *NotePad = &NotePad{Level: LevelDebug, Handle: os.Stdout, Logger: &DEBUG, Prefix: "DEBUG: "}
	info            *NotePad = &NotePad{Level: LevelInfo, Handle: os.Stdout, Logger: &INFO, Prefix: "INFO: "}
	warn            *NotePad = &NotePad{Level: LevelWarn, Handle: os.Stdout, Logger: &WARN, Prefix: "WARN: "}
	err             *NotePad = &NotePad{Level: LevelError, Handle: os.Stdout, Logger: &ERROR, Prefix: "ERROR: "}
	critical        *NotePad = &NotePad{Level: LevelCritical, Handle: os.Stdout, Logger: &CRITICAL, Prefix: "CRITICAL: "}
	fatal           *NotePad = &NotePad{Level: LevelFatal, Handle: os.Stdout, Logger: &FATAL, Prefix: "FATAL: "}
	logThreshold    Level    = DefaultLogThreshold
	outputThreshold Level    = DefaultStdoutThreshold
)

func init() {
	SetStdoutThreshold(DefaultStdoutThreshold)
}

// initialize will setup the jWalterWeatherman standard approach of providing the user
// some feedback and logging a potentially different amount based on independent log and output thresholds.
// By default the output has a lower threshold than logged
// Don't use if you have manually set the Handles of the different levels as it will overwrite them.
func initialize() {
	BothHandle = io.MultiWriter(LogHandle, OutHandle)

	for _, n := range NotePads {
		if n.Level < outputThreshold && n.Level < logThreshold {
			n.Handle = ioutil.Discard
		} else if n.Level >= outputThreshold && n.Level >= logThreshold {
			n.Handle = BothHandle
		} else if n.Level >= outputThreshold && n.Level < logThreshold {
			n.Handle = OutHandle
		} else {
			n.Handle = LogHandle
		}
	}

	for _, n := range NotePads {
		*n.Logger = log.New(n.Handle, n.Prefix, log.Ldate)
	}

	LOG = log.New(LogHandle,
		"LOG:   ",
		log.Ldate|log.Ltime|log.Lshortfile)
}

// Level returns the current global log threshold.
func LogThreshold() Level {
	return logThreshold
}

// Level returns the current global output threshold.
func StdoutThreshold() Level {
	return outputThreshold
}

// Ensures that the level provided is within the bounds of available levels
func levelCheck(level Level) Level {
	switch {
	case level <= LevelTrace:
		return LevelTrace
	case level >= LevelFatal:
		return LevelFatal
	default:
		return level
	}
}

// Establishes a threshold where anything matching or above will be logged
func SetLogThreshold(level Level) {
	logThreshold = levelCheck(level)
	initialize()
}

// Establishes a threshold where anything matching or above will be output
func SetStdoutThreshold(level Level) {
	outputThreshold = levelCheck(level)
	initialize()
}

// Conveniently Sets the Log Handle to a io.writer created for the file behind the given filepath
// Will only append to this file
func SetLogFile(path string) {
	file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		CRITICAL.Println("Failed to open log file:", path, err)
		os.Exit(-1)
	}

	INFO.Println("Logging to", file.Name())

	LogHandle = file
	initialize()
}

// Conveniently Creates a temporary file and sets the Log Handle to a io.writer created for it
func UseTempLogFile(prefix string) {
	file, err := ioutil.TempFile(os.TempDir(), prefix)
	if err != nil {
		CRITICAL.Println(err)
	}

	INFO.Println("Logging to", file.Name())

	LogHandle = file
	initialize()
}

// Disables logging for the entire JWW system
func DiscardLogging() {
	LogHandle = ioutil.Discard
	initialize()
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Println(v ...interface{}) {
	fmt.Println(v...)
	LOG.Println(v...)
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Printf(format string, v ...interface{}) {
	fmt.Printf(format, v...)
	LOG.Printf(format, v...)
}
<commit_msg>Output caller's file name and line at FEEDBACK<commit_after>// Copyright © 2014 Steve Francia <spf@spf13.com>.
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package jwalterweatherman

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
)

// Level describes the chosen log level between
// debug and critical.
type Level int

type NotePad struct {
	Handle io.Writer
	Level  Level
	Prefix string
	Logger **log.Logger
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
type Feedback struct{}

const (
	LevelTrace Level = iota
	LevelDebug
	LevelInfo
	LevelWarn
	LevelError
	LevelCritical
	LevelFatal
	DefaultLogThreshold    = LevelWarn
	DefaultStdoutThreshold = LevelError
)

var (
	TRACE      *log.Logger
	DEBUG      *log.Logger
	INFO       *log.Logger
	WARN       *log.Logger
	ERROR      *log.Logger
	CRITICAL   *log.Logger
	FATAL      *log.Logger
	LOG        *log.Logger
	FEEDBACK   Feedback
	LogHandle  io.Writer  = ioutil.Discard
	OutHandle  io.Writer  = os.Stdout
	BothHandle io.Writer  = io.MultiWriter(LogHandle, OutHandle)
	NotePads   []*NotePad = []*NotePad{trace, debug, info, warn, err, critical, fatal}

	trace           *NotePad = &NotePad{Level: LevelTrace, Handle: os.Stdout, Logger: &TRACE, Prefix: "TRACE: "}
	debug           *NotePad = &NotePad{Level: LevelDebug, Handle: os.Stdout, Logger: &DEBUG, Prefix: "DEBUG: "}
	info            *NotePad = &NotePad{Level: LevelInfo, Handle: os.Stdout, Logger: &INFO, Prefix: "INFO: "}
	warn            *NotePad = &NotePad{Level: LevelWarn, Handle: os.Stdout, Logger: &WARN, Prefix: "WARN: "}
	err             *NotePad = &NotePad{Level: LevelError, Handle: os.Stdout, Logger: &ERROR, Prefix: "ERROR: "}
	critical        *NotePad = &NotePad{Level: LevelCritical, Handle: os.Stdout, Logger: &CRITICAL, Prefix: "CRITICAL: "}
	fatal           *NotePad = &NotePad{Level: LevelFatal, Handle: os.Stdout, Logger: &FATAL, Prefix: "FATAL: "}
	logThreshold    Level    = DefaultLogThreshold
	outputThreshold Level    = DefaultStdoutThreshold
)

func init() {
	SetStdoutThreshold(DefaultStdoutThreshold)
}

// initialize will setup the jWalterWeatherman standard approach of providing the user
// some feedback and logging a potentially different amount based on independent log and output thresholds.
// By default the output has a lower threshold than logged
// Don't use if you have manually set the Handles of the different levels as it will overwrite them.
func initialize() {
	BothHandle = io.MultiWriter(LogHandle, OutHandle)

	for _, n := range NotePads {
		if n.Level < outputThreshold && n.Level < logThreshold {
			n.Handle = ioutil.Discard
		} else if n.Level >= outputThreshold && n.Level >= logThreshold {
			n.Handle = BothHandle
		} else if n.Level >= outputThreshold && n.Level < logThreshold {
			n.Handle = OutHandle
		} else {
			n.Handle = LogHandle
		}
	}

	for _, n := range NotePads {
		*n.Logger = log.New(n.Handle, n.Prefix, log.Ldate)
	}

	LOG = log.New(LogHandle,
		"LOG:   ",
		log.Ldate|log.Ltime|log.Lshortfile)
}

// Level returns the current global log threshold.
func LogThreshold() Level {
	return logThreshold
}

// Level returns the current global output threshold.
func StdoutThreshold() Level {
	return outputThreshold
}

// Ensures that the level provided is within the bounds of available levels
func levelCheck(level Level) Level {
	switch {
	case level <= LevelTrace:
		return LevelTrace
	case level >= LevelFatal:
		return LevelFatal
	default:
		return level
	}
}

// Establishes a threshold where anything matching or above will be logged
func SetLogThreshold(level Level) {
	logThreshold = levelCheck(level)
	initialize()
}

// Establishes a threshold where anything matching or above will be output
func SetStdoutThreshold(level Level) {
	outputThreshold = levelCheck(level)
	initialize()
}

// Conveniently Sets the Log Handle to a io.writer created for the file behind the given filepath
// Will only append to this file
func SetLogFile(path string) {
	file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		CRITICAL.Println("Failed to open log file:", path, err)
		os.Exit(-1)
	}

	INFO.Println("Logging to", file.Name())

	LogHandle = file
	initialize()
}

// Conveniently Creates a temporary file and sets the Log Handle to a io.writer created for it
func UseTempLogFile(prefix string) {
	file, err := ioutil.TempFile(os.TempDir(), prefix)
	if err != nil {
		CRITICAL.Println(err)
	}

	INFO.Println("Logging to", file.Name())

	LogHandle = file
	initialize()
}

// Disables logging for the entire JWW system
func DiscardLogging() {
	LogHandle = ioutil.Discard
	initialize()
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Println(v ...interface{}) {
	s := fmt.Sprintln(v...)
	fmt.Print(s)
	LOG.Output(2, s)
}

// Feedback is special. It writes plainly to the output while
// logging with the standard extra information (date, file, etc)
// Only Println and Printf are currently provided for this
func (fb *Feedback) Printf(format string, v ...interface{}) {
	s := fmt.Sprintf(format, v...)
	fmt.Print(s)
	LOG.Output(2, s)
}
<|endoftext|>


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

package themefiles

const ScreenCss = `
html {
    min-height: 100%;
    font-size: 100%;
    overflow-y: scroll;
    -webkit-text-size-adjust: 100%;
    -ms-text-size-adjust: 100%;
}

body {
    color: #444;
    font-family: Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', "Hiragino Sans GB", "STXihei", "微软雅黑", serif;
    font-size: 12px;
    line-height: 1.5em;
    background: #fefefe;
    width: 75%;
    min-height: 100%;
    margin: 0px auto 10px auto;
    padding: 0 1em;
    outline: 1300px solid #FAFAFA;
}

.cleaner {
    clear: both;
}

a {
    color: #0645ad;
    text-decoration: none;
}

a:visited {
    color: #0b0080;
}

a:hover {
    color: #06e;
}

a:active {
    color: #faa700;
}

a:focus {
    outline: thin dotted;
}

a:hover, a:active {
    outline: 0;
}

span.backtick {
    border: 1px solid #EAEAEA;
    border-radius: 3px;
    background: #F8F8F8;
    padding: 0 3px 0 3px;
}

::-moz-selection {
    background: rgba(255,255,0,0.3);
    color: #000;
}

::selection {
    background: rgba(255,255,0,0.3);
    color: #000;
}

a::-moz-selection {
    background: rgba(255,255,0,0.3);
    color: #0645ad;
}

a::selection {
    background: rgba(255,255,0,0.3);
    color: #0645ad;
}

p {
    margin: 1em 0;
}

img {
    max-width: 100%;
}

h1,h2,h3,h4,h5,h6 {
    font-weight: normal;
    color: #111;
    line-height: 1em;
}

h4,h5,h6 {
    font-weight: bold;
}

h1 {
    font-size: 2.5em;
    margin: 0 0 15px 0;
}

h2 {
    font-size: 2em;
    border-bottom: 1px solid silver;
    padding-bottom: 5px;
}

h3 {
    font-size: 1.5em;
}

h4 {
    font-size: 1.2em;
}

h5 {
    font-size: 1em;
}

h6 {
    font-size: 0.9em;
}

blockquote {
    color: #666666;
    margin: 0;
    padding-left: 3em;
    border-left: 0.5em #EEE solid;
}

hr {
    display: block;
    height: 2px;
    border: 0;
    border-top: 1px solid #aaa;
    border-bottom: 1px solid #eee;
    margin: 1em 0;
    padding: 0;
}

pre , code, kbd, samp {
    color: #000;
    font-family: monospace;
    font-size: 0.88em;
    border-radius: 3px;
    background-color: #F8F8F8;
    border: 1px solid #CCC;
}

pre {
    white-space: pre;
    white-space: pre-wrap;
    word-wrap: break-word;
    padding: 5px 12px;
}

pre code {
    border: 0px !important;
    padding: 0;
}

code {
    padding: 0 3px 0 3px;
}

b, strong {
    font-weight: bold;
}

dfn {
    font-style: italic;
}

ins {
    background: #ff9;
    color: #000;
    text-decoration: none;
}

mark {
    background: #ff0;
    color: #000;
    font-style: italic;
    font-weight: bold;
}

sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
}

sup {
    top: -0.5em;
}

sub {
    bottom: -0.25em;
}

ul, ol {
    margin: 0;
    padding: 0 0 0 2em;
}

li p:last-child {
    margin: 0;
}

dd {
    margin: 0 0 0 2em;
}

img {
    border: 0;
    -ms-interpolation-mode: bicubic;
    vertical-align: middle;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

td {
    vertical-align: top;
}

body>footer {
    margin: 25px 0 0 0;
    border-top: 1px solid #eee;
}

body>footer>nav {
    text-align: right;
}

body>footer>nav>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>footer>nav>ul>li>a {
    color: #000000;
    font-size: 0.8em;
}

body>nav.toplevel {
    text-align: right;
    font-size: 1.1em;
    margin: 0 0 15px 0;
    padding: 0;
    clear: both;
}

body>nav.toplevel>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>nav.toplevel>ul>li {
    display: inline-block;
    border: 1px solid #000000;
    background-color: #FFFFFF;
    margin: -1px -5px 0 -2px;
    padding: 0px 10px;
    white-space: nowrap;
}

body>nav.toplevel>ul>li:last-child {
    border-right: 1px solid #000000;
    clear: both;
}

body>nav.toplevel>ul>li>a {
    font-family: "Helvetia", "Verdana", "Sans-Serif";
    color: #000000;
}

body>nav.toplevel>ul>li>a:hover {
    color: #06e;
}

body>nav.breadcrumb {
    font-size: 0.8em;
}

body>nav.breadcrumb>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>nav.breadcrumb>ul>li {
    display: inline;
}

body>nav.breadcrumb>ul>li:after {
    content: "»";
}

body>nav.breadcrumb>ul>li:last-child:after {
    content: "";
}

body>nav.breadcrumb>ul>li>a {
    font-family: "Helvetia", "Verdana", "Sans-Serif";
}

article>.description {
    font-size: 1.2em;
}

.childs {
    margin: 25px 0 0 0;
}

.childs>.list {
    list-style: none;
    padding: 5px 0 5px 0;
    margin: 0 0 0 15px;
}

.childs>.list>.child {
    margin: 0 0 15px 0;
}

.childs>.list>.child:nth-child(odd) {
    background-color:#eee;
}

.childs>.list>.child:nth-child(even) {
    background-color:transparent;
}

.imagegallery>h1 {
    font-size: 1.2em;
}

.imagegallery ol {
    list-style: none;
    margin-left: 0;
}

.filelinks>h1 {
    font-size: 1.2em;
}

.filelinks ol {
    margin-left: 0;
}

.csv {
    margin: 20px 0 0 20px;
    overflow: auto;
}

.csv>h1 {
    font-size: 1.2em;
}

.csv>table
{
    font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
    font-size: 1.0em;
    margin: 10px 45px 10px 45px;
    text-align: left;
    border-collapse: collapse;
    border: 1px solid #69c;
}

.csv>table thead
{
    padding: 12px 17px 12px 17px;
    font-weight: normal;
    font-size: 1.2em;
    color: #039;
    border-bottom: 1px dashed #69c;
}

.csv>table td
{
    padding: 7px 17px 7px 17px;
    color: #669;
}

.csv>table tbody tr:hover td
{
    color: #339;
    background: #d0dafd;
}

.pdf {
    width: 80%;
    max-height: 80%;
}

.pdf>h1 {
    font-size: 1.2em;
}

.pdf .metadata {
    float: left;
    font-size: 1.0em;
    margin: 0 15px 0 0;
}

.pdf .metadata .entry {
    margin: 0 0 10px 0;
}

.pdf .previewarea {
    display: block;
    overflow: auto;
    box-shadow: 0 0 10px #000000;
}

.pdf .previewarea canvas {
    float: none;
}

.video>h1 {
    font-size: 1.2em;
}

.audio>h1 {
    font-size: 1.2em;
}

.presentation nav {
    float: left;
    margin: 25px 0 15px 0;
    width: 100%;
    text-align: center;
}

.presentation nav .controls {
    float: left;
}

.presentation nav .pager {
    display: inline;
    cursor: default;
}

.presentation nav .jumper {
    float: right;
}

.presentation .content {
    clear: left;
}

.presentation .slide {
    float: none;
    box-shadow: 0 0 10px #000000;
    padding: 10px;
}

/* first level */
.sitemap>.content>ol {
    list-style: none;
    padding: 0;
    margin: 15px 0 0 0;
}

/* second level */
.sitemap>.content>ol>li>ol {
    list-style-type: lower-alpha;
    padding: 0 40px;
}

.sitemap>.content>ol>li>ol>li {
    padding: 15px 0;
}

/* third, forth, n-th level */
.sitemap>.content>ol>li>ol>li ol {
    list-style-type: decimal;
}

@media only screen and (max-height: 500px) {
    body>article {
        min-height: 300px;
    }

    .presentation .slide {
        min-height: 220px;
    }
}

@media only screen and (min-height: 500px) {
    body>article {
        min-height: 500px;
    }

    .presentation .slide {
        min-height: 320px;
    }
}

@media only screen and (min-height: 600px) {
    body>article {
        min-height: 600px;
    }

    .presentation .slide {
        min-height: 420px;
    }
}

@media only screen and (min-height: 768px) {
    body>article {
        min-height: 768px;
    }

    .presentation .slide {
        min-height: 520px;
    }
}

@media only screen and (max-width: 480px) {
    body {
        font-size: 12px;
        width: 95%;
    }

    .presentation nav {
        margin: 25px 0 15px 0;
        width: 100%;
    }

    .presentation nav .pager {
        float: right;
    }

    .presentation nav .jumper {
        display: none;
    }
}

@media only screen and (min-width: 480px) {
    body {
        font-size: 14px;
        width: 95%;
    }
}

@media only screen and (min-width: 768px) {
    body {
        font-size: 16px;
        width: 95%;
    }
}

@media only screen and (min-width: 1024px) {
    body {
        font-size: 16px;
        width: 75%;
    }
}`
<commit_msg>Default theme update: Increase the top and bottom margin for the video player<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package themefiles

const ScreenCss = `
html {
    min-height: 100%;
    font-size: 100%;
    overflow-y: scroll;
    -webkit-text-size-adjust: 100%;
    -ms-text-size-adjust: 100%;
}

body {
    color: #444;
    font-family: Georgia, Palatino, 'Palatino Linotype', Times, 'Times New Roman', "Hiragino Sans GB", "STXihei", "微软雅黑", serif;
    font-size: 12px;
    line-height: 1.5em;
    background: #fefefe;
    width: 75%;
    min-height: 100%;
    margin: 0px auto 10px auto;
    padding: 0 1em;
    outline: 1300px solid #FAFAFA;
}

.cleaner {
    clear: both;
}

a {
    color: #0645ad;
    text-decoration: none;
}

a:visited {
    color: #0b0080;
}

a:hover {
    color: #06e;
}

a:active {
    color: #faa700;
}

a:focus {
    outline: thin dotted;
}

a:hover, a:active {
    outline: 0;
}

span.backtick {
    border: 1px solid #EAEAEA;
    border-radius: 3px;
    background: #F8F8F8;
    padding: 0 3px 0 3px;
}

::-moz-selection {
    background: rgba(255,255,0,0.3);
    color: #000;
}

::selection {
    background: rgba(255,255,0,0.3);
    color: #000;
}

a::-moz-selection {
    background: rgba(255,255,0,0.3);
    color: #0645ad;
}

a::selection {
    background: rgba(255,255,0,0.3);
    color: #0645ad;
}

p {
    margin: 1em 0;
}

img {
    max-width: 100%;
}

h1,h2,h3,h4,h5,h6 {
    font-weight: normal;
    color: #111;
    line-height: 1em;
}

h4,h5,h6 {
    font-weight: bold;
}

h1 {
    font-size: 2.5em;
    margin: 0 0 15px 0;
}

h2 {
    font-size: 2em;
    border-bottom: 1px solid silver;
    padding-bottom: 5px;
}

h3 {
    font-size: 1.5em;
}

h4 {
    font-size: 1.2em;
}

h5 {
    font-size: 1em;
}

h6 {
    font-size: 0.9em;
}

blockquote {
    color: #666666;
    margin: 0;
    padding-left: 3em;
    border-left: 0.5em #EEE solid;
}

hr {
    display: block;
    height: 2px;
    border: 0;
    border-top: 1px solid #aaa;
    border-bottom: 1px solid #eee;
    margin: 1em 0;
    padding: 0;
}

pre , code, kbd, samp {
    color: #000;
    font-family: monospace;
    font-size: 0.88em;
    border-radius: 3px;
    background-color: #F8F8F8;
    border: 1px solid #CCC;
}

pre {
    white-space: pre;
    white-space: pre-wrap;
    word-wrap: break-word;
    padding: 5px 12px;
}

pre code {
    border: 0px !important;
    padding: 0;
}

code {
    padding: 0 3px 0 3px;
}

b, strong {
    font-weight: bold;
}

dfn {
    font-style: italic;
}

ins {
    background: #ff9;
    color: #000;
    text-decoration: none;
}

mark {
    background: #ff0;
    color: #000;
    font-style: italic;
    font-weight: bold;
}

sub, sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
}

sup {
    top: -0.5em;
}

sub {
    bottom: -0.25em;
}

ul, ol {
    margin: 0;
    padding: 0 0 0 2em;
}

li p:last-child {
    margin: 0;
}

dd {
    margin: 0 0 0 2em;
}

img {
    border: 0;
    -ms-interpolation-mode: bicubic;
    vertical-align: middle;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

td {
    vertical-align: top;
}

body>footer {
    margin: 25px 0 0 0;
    border-top: 1px solid #eee;
}

body>footer>nav {
    text-align: right;
}

body>footer>nav>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>footer>nav>ul>li>a {
    color: #000000;
    font-size: 0.8em;
}

body>nav.toplevel {
    text-align: right;
    font-size: 1.1em;
    margin: 0 0 15px 0;
    padding: 0;
    clear: both;
}

body>nav.toplevel>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>nav.toplevel>ul>li {
    display: inline-block;
    border: 1px solid #000000;
    background-color: #FFFFFF;
    margin: -1px -5px 0 -2px;
    padding: 0px 10px;
    white-space: nowrap;
}

body>nav.toplevel>ul>li:last-child {
    border-right: 1px solid #000000;
    clear: both;
}

body>nav.toplevel>ul>li>a {
    font-family: "Helvetia", "Verdana", "Sans-Serif";
    color: #000000;
}

body>nav.toplevel>ul>li>a:hover {
    color: #06e;
}

body>nav.breadcrumb {
    font-size: 0.8em;
}

body>nav.breadcrumb>ul {
    list-style: none;
    margin: 0;
    padding: 0;
}

body>nav.breadcrumb>ul>li {
    display: inline;
}

body>nav.breadcrumb>ul>li:after {
    content: "»";
}

body>nav.breadcrumb>ul>li:last-child:after {
    content: "";
}

body>nav.breadcrumb>ul>li>a {
    font-family: "Helvetia", "Verdana", "Sans-Serif";
}

article>.description {
    font-size: 1.2em;
}

.childs {
    margin: 25px 0 0 0;
}

.childs>.list {
    list-style: none;
    padding: 5px 0 5px 0;
    margin: 0 0 0 15px;
}

.childs>.list>.child {
    margin: 0 0 15px 0;
}

.childs>.list>.child:nth-child(odd) {
    background-color:#eee;
}

.childs>.list>.child:nth-child(even) {
    background-color:transparent;
}

.imagegallery>h1 {
    font-size: 1.2em;
}

.imagegallery ol {
    list-style: none;
    margin-left: 0;
}

.filelinks>h1 {
    font-size: 1.2em;
}

.filelinks ol {
    margin-left: 0;
}

.csv {
    margin: 20px 0 0 20px;
    overflow: auto;
}

.csv>h1 {
    font-size: 1.2em;
}

.csv>table
{
    font-family: "Lucida Sans Unicode", "Lucida Grande", Sans-Serif;
    font-size: 1.0em;
    margin: 10px 45px 10px 45px;
    text-align: left;
    border-collapse: collapse;
    border: 1px solid #69c;
}

.csv>table thead
{
    padding: 12px 17px 12px 17px;
    font-weight: normal;
    font-size: 1.2em;
    color: #039;
    border-bottom: 1px dashed #69c;
}

.csv>table td
{
    padding: 7px 17px 7px 17px;
    color: #669;
}

.csv>table tbody tr:hover td
{
    color: #339;
    background: #d0dafd;
}

.pdf {
    width: 80%;
    max-height: 80%;
}

.pdf>h1 {
    font-size: 1.2em;
}

.pdf .metadata {
    float: left;
    font-size: 1.0em;
    margin: 0 15px 0 0;
}

.pdf .metadata .entry {
    margin: 0 0 10px 0;
}

.pdf .previewarea {
    display: block;
    overflow: auto;
    box-shadow: 0 0 10px #000000;
}

.pdf .previewarea canvas {
    float: none;
}

section.video {
    margin: 15px 0;
}

section.video>h1 {
    font-size: 1.2em;
}

.audio>h1 {
    font-size: 1.2em;
}

.presentation nav {
    float: left;
    margin: 25px 0 15px 0;
    width: 100%;
    text-align: center;
}

.presentation nav .controls {
    float: left;
}

.presentation nav .pager {
    display: inline;
    cursor: default;
}

.presentation nav .jumper {
    float: right;
}

.presentation .content {
    clear: left;
}

.presentation .slide {
    float: none;
    box-shadow: 0 0 10px #000000;
    padding: 10px;
}

/* first level */
.sitemap>.content>ol {
    list-style: none;
    padding: 0;
    margin: 15px 0 0 0;
}

/* second level */
.sitemap>.content>ol>li>ol {
    list-style-type: lower-alpha;
    padding: 0 40px;
}

.sitemap>.content>ol>li>ol>li {
    padding: 15px 0;
}

/* third, forth, n-th level */
.sitemap>.content>ol>li>ol>li ol {
    list-style-type: decimal;
}

@media only screen and (max-height: 500px) {
    body>article {
        min-height: 300px;
    }

    .presentation .slide {
        min-height: 220px;
    }
}

@media only screen and (min-height: 500px) {
    body>article {
        min-height: 500px;
    }

    .presentation .slide {
        min-height: 320px;
    }
}

@media only screen and (min-height: 600px) {
    body>article {
        min-height: 600px;
    }

    .presentation .slide {
        min-height: 420px;
    }
}

@media only screen and (min-height: 768px) {
    body>article {
        min-height: 768px;
    }

    .presentation .slide {
        min-height: 520px;
    }
}

@media only screen and (max-width: 480px) {
    body {
        font-size: 12px;
        width: 95%;
    }

    .presentation nav {
        margin: 25px 0 15px 0;
        width: 100%;
    }

    .presentation nav .pager {
        float: right;
    }

    .presentation nav .jumper {
        display: none;
    }
}

@media only screen and (min-width: 480px) {
    body {
        font-size: 14px;
        width: 95%;
    }
}

@media only screen and (min-width: 768px) {
    body {
        font-size: 16px;
        width: 95%;
    }
}

@media only screen and (min-width: 1024px) {
    body {
        font-size: 16px;
        width: 75%;
    }
}`
<|endoftext|>


<commit_before>package run

import (
	"fmt"
	"syscall"
	"testing"

	"github.com/anonx/sunplate/command"
)

func TestExecute_IncorrectTask(t *testing.T) {
	ts := []string{
		"gxgbhsjdjdduuhdhsh", // This is expected to cause a panic.
	}
	defer expectPanic(fmt.Sprintf(`Command "%s" does not exist. Panic expected.`, ts[0]))
	execute(ts)
}

func TestExecute(t *testing.T) {
	ts := []string{
		passCommand,
		`sunplate echo "Hello, world!"`,
		`sunplate echo "This is test"`,
	}
	execute(ts)
}

func TestUserCommand(t *testing.T) {
	imp := "github.com/anonx/sunplate/skeleton"
	res := userCommand(":application", imp)
	exp := "skeleton"
	if res != exp {
		t.Errorf("Incorrect user command. Expected `%s`, got `%s`.", exp, res)
	}
}

func TestRun_IncorrectTask(t *testing.T) {
	c := "gxgbhsjdjdduuhdhsh"
	defer expectPanic(fmt.Sprintf(`Command "%s" does not exist. Panic expected.`, c))
	run(c)
}

func TestRun(t *testing.T) {
	run("sleep 10")
	cmd := run("sleep 1") // Starting a new instance.
	err := cmd.Wait()
	if err != nil {
		t.Error("Command is not started at the time of check.")
	}
}

func TestStart_EmptyFile(t *testing.T) {
	notify <- syscall.SIGTERM
	start("run", command.Data{
		"run": "github.com/anonx/sunplate/run/testdata/empty",
	})
}

func TestStart(t *testing.T) {
	defer expectPanic("Application has been killed, panic expected.")
	notify <- syscall.SIGTERM
	start("run", command.Data{
		"run": "github.com/anonx/sunplate/run/testdata/nonempty",
	})
}

func TestRebuildFunc(t *testing.T) {
	ts := []string{`sunplate echo "task 1"`, `sunplate echo "task 2"`}
	as := []string{`sunplate echo "after 1"`, `sunplate echo "after 2"`}
	c := `sunplate echo "command"`
	fn := rebuildFunc(ts, as, c)
	fn()
}

func expectPanic(msg string) {
	if err := recover(); err == nil {
		panic(msg)
	}
}
<commit_msg>Replaced sleep in run_tests for cross platform support<commit_after>package run

import (
	"fmt"
	"syscall"
	"testing"

	"github.com/anonx/sunplate/command"
)

func TestExecute_IncorrectTask(t *testing.T) {
	ts := []string{
		"gxgbhsjdjdduuhdhsh", // This is expected to cause a panic.
	}
	defer expectPanic(fmt.Sprintf(`Command "%s" does not exist. Panic expected.`, ts[0]))
	execute(ts)
}

func TestExecute(t *testing.T) {
	ts := []string{
		passCommand,
		`sunplate echo "Hello, world!"`,
		`sunplate echo "This is test"`,
	}
	execute(ts)
}

func TestUserCommand(t *testing.T) {
	imp := "github.com/anonx/sunplate/skeleton"
	res := userCommand(":application", imp)
	exp := "skeleton"
	if res != exp {
		t.Errorf("Incorrect user command. Expected `%s`, got `%s`.", exp, res)
	}
}

func TestRun_IncorrectTask(t *testing.T) {
	c := "gxgbhsjdjdduuhdhsh"
	defer expectPanic(fmt.Sprintf(`Command "%s" does not exist. Panic expected.`, c))
	run(c)
}

func TestRun(t *testing.T) {
	run("sunplate run github.com/anonx/sunplate/skeleton") // A program that runs forever.
	cmd := run(`sunplate echo "Starting a new instance"`)  // This should kill the previous instance.
	err := cmd.Wait()
	if err != nil {
		t.Error("Command is not started at the time of check.")
	}
}

func TestStart_EmptyFile(t *testing.T) {
	notify <- syscall.SIGTERM
	start("run", command.Data{
		"run": "github.com/anonx/sunplate/run/testdata/empty",
	})
}

func TestStart(t *testing.T) {
	defer expectPanic("Application has been killed, panic expected.")
	notify <- syscall.SIGTERM
	start("run", command.Data{
		"run": "github.com/anonx/sunplate/run/testdata/nonempty",
	})
}

func TestRebuildFunc(t *testing.T) {
	ts := []string{`sunplate echo "task 1"`, `sunplate echo "task 2"`}
	as := []string{`sunplate echo "after 1"`, `sunplate echo "after 2"`}
	c := `sunplate echo "command"`
	fn := rebuildFunc(ts, as, c)
	fn()
}

func expectPanic(msg string) {
	if err := recover(); err == nil {
		panic(msg)
	}
}
<|endoftext|>