<commit_before>//
// Copyright 2017, Arkbriar
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"io"
	"time"
)

// JobsService handles communication with the ci builds related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
type JobsService struct {
	client *Client
}

// Job represents a ci build.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
type Job struct {
	Commit            *Commit    `json:"commit"`
	Coverage          float64    `json:"coverage"`
	AllowFailure      bool       `json:"allow_failure"`
	CreatedAt         *time.Time `json:"created_at"`
	StartedAt         *time.Time `json:"started_at"`
	FinishedAt        *time.Time `json:"finished_at"`
	Duration          float64    `json:"duration"`
	ArtifactsExpireAt *time.Time `json:"artifacts_expire_at"`
	ID                int        `json:"id"`
	Name              string     `json:"name"`
	Pipeline          struct {
		ID     int    `json:"id"`
		Ref    string `json:"ref"`
		Sha    string `json:"sha"`
		Status string `json:"status"`
	} `json:"pipeline"`
	Ref       string `json:"ref"`
	Artifacts []struct {
		FileType   string `json:"file_type"`
		Filename   string `json:"filename"`
		Size       int    `json:"size"`
		FileFormat string `json:"file_format"`
	} `json:"artifacts"`
	ArtifactsFile struct {
		Filename string `json:"filename"`
		Size     int    `json:"size"`
	} `json:"artifacts_file"`
	Runner struct {
		ID          int    `json:"id"`
		Description string `json:"description"`
		Active      bool   `json:"active"`
		IsShared    bool   `json:"is_shared"`
		Name        string `json:"name"`
	} `json:"runner"`
	Stage  string `json:"stage"`
	Status string `json:"status"`
	Tag    bool   `json:"tag"`
	WebURL string `json:"web_url"`
	User   *User  `json:"user"`
}

// ListJobsOptions are options for two list apis
type ListJobsOptions struct {
	ListOptions
	Scope []BuildStateValue `url:"scope[],omitempty" json:"scope,omitempty"`
}

// ListProjectJobs gets a list of jobs in a project.
//
// The scope of jobs to show, one or array of: created, pending, running,
// failed, success, canceled, skipped; showing all jobs if none provided
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...RequestOptionFunc) ([]Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opts, options)
	if err != nil {
		return nil, nil, err
	}

	var jobs []Job
	resp, err := s.client.Do(req, &jobs)
	if err != nil {
		return nil, resp, err
	}

	return jobs, resp, err
}

// ListPipelineJobs gets a list of jobs for specific pipeline in a
// project. If the pipeline ID is not found, it will respond with 404.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...RequestOptionFunc) ([]*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", pathEscape(project), pipelineID)

	req, err := s.client.NewRequest("GET", u, opts, options)
	if err != nil {
		return nil, nil, err
	}

	var jobs []*Job
	resp, err := s.client.Do(req, &jobs)
	if err != nil {
		return nil, resp, err
	}

	return jobs, resp, err
}

// GetJob gets a single job of a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
func (s *JobsService) GetJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// GetJobArtifacts get jobs artifacts of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#get-job-artifacts
func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	artifactsBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactsBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactsBuf, resp, err
}

// DownloadArtifactsFileOptions represents the available DownloadArtifactsFile()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-the-artifacts-archive
type DownloadArtifactsFileOptions struct {
	Job *string `url:"job" json:"job"`
}

// DownloadArtifactsFile download the artifacts file from the given
// reference name and job provided the job finished successfully.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-the-artifacts-archive
func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, opt *DownloadArtifactsFileOptions, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download", pathEscape(project), refName)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	artifactsBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactsBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactsBuf, resp, err
}

// DownloadSingleArtifactsFile download a file from the artifacts from the
// given reference name and job provided the job finished successfully.
// Only a single file is going to be extracted from the archive and streamed
// to a client.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-a-single-artifact-file-by-job-id
func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, artifactPath string, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}

	u := fmt.Sprintf(
		"projects/%s/jobs/%d/artifacts/%s",
		pathEscape(project),
		jobID,
		artifactPath,
	)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	artifactBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactBuf, resp, err
}

// GetTraceFile gets a trace of a specific job of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/trace", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	traceBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, traceBuf)
	if err != nil {
		return nil, resp, err
	}

	return traceBuf, resp, err
}

// CancelJob cancels a single job of a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/cancel", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// RetryJob retries a single job of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/retry", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// EraseJob erases a single job of a project, removes a job
// artifacts and a job trace.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/erase", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// KeepArtifacts prevents artifacts from being deleted when
// expiration is set.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#keep-artifacts
func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// PlayJob triggers a manual action to start a job.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/play", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}
<commit_msg>Add method to delete artifacts for a job<commit_after>//
// Copyright 2017, Arkbriar
//
// 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 gitlab

import (
	"bytes"
	"fmt"
	"io"
	"time"
)

// JobsService handles communication with the ci builds related methods
// of the GitLab API.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
type JobsService struct {
	client *Client
}

// Job represents a ci build.
//
// GitLab API docs: https://docs.gitlab.com/ce/api/jobs.html
type Job struct {
	Commit            *Commit    `json:"commit"`
	Coverage          float64    `json:"coverage"`
	AllowFailure      bool       `json:"allow_failure"`
	CreatedAt         *time.Time `json:"created_at"`
	StartedAt         *time.Time `json:"started_at"`
	FinishedAt        *time.Time `json:"finished_at"`
	Duration          float64    `json:"duration"`
	ArtifactsExpireAt *time.Time `json:"artifacts_expire_at"`
	ID                int        `json:"id"`
	Name              string     `json:"name"`
	Pipeline          struct {
		ID     int    `json:"id"`
		Ref    string `json:"ref"`
		Sha    string `json:"sha"`
		Status string `json:"status"`
	} `json:"pipeline"`
	Ref       string `json:"ref"`
	Artifacts []struct {
		FileType   string `json:"file_type"`
		Filename   string `json:"filename"`
		Size       int    `json:"size"`
		FileFormat string `json:"file_format"`
	} `json:"artifacts"`
	ArtifactsFile struct {
		Filename string `json:"filename"`
		Size     int    `json:"size"`
	} `json:"artifacts_file"`
	Runner struct {
		ID          int    `json:"id"`
		Description string `json:"description"`
		Active      bool   `json:"active"`
		IsShared    bool   `json:"is_shared"`
		Name        string `json:"name"`
	} `json:"runner"`
	Stage  string `json:"stage"`
	Status string `json:"status"`
	Tag    bool   `json:"tag"`
	WebURL string `json:"web_url"`
	User   *User  `json:"user"`
}

// ListJobsOptions are options for two list apis
type ListJobsOptions struct {
	ListOptions
	Scope []BuildStateValue `url:"scope[],omitempty" json:"scope,omitempty"`
}

// ListProjectJobs gets a list of jobs in a project.
//
// The scope of jobs to show, one or array of: created, pending, running,
// failed, success, canceled, skipped; showing all jobs if none provided
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#list-project-jobs
func (s *JobsService) ListProjectJobs(pid interface{}, opts *ListJobsOptions, options ...RequestOptionFunc) ([]Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs", pathEscape(project))

	req, err := s.client.NewRequest("GET", u, opts, options)
	if err != nil {
		return nil, nil, err
	}

	var jobs []Job
	resp, err := s.client.Do(req, &jobs)
	if err != nil {
		return nil, resp, err
	}

	return jobs, resp, err
}

// ListPipelineJobs gets a list of jobs for specific pipeline in a
// project. If the pipeline ID is not found, it will respond with 404.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#list-pipeline-jobs
func (s *JobsService) ListPipelineJobs(pid interface{}, pipelineID int, opts *ListJobsOptions, options ...RequestOptionFunc) ([]*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/pipelines/%d/jobs", pathEscape(project), pipelineID)

	req, err := s.client.NewRequest("GET", u, opts, options)
	if err != nil {
		return nil, nil, err
	}

	var jobs []*Job
	resp, err := s.client.Do(req, &jobs)
	if err != nil {
		return nil, resp, err
	}

	return jobs, resp, err
}

// GetJob gets a single job of a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#get-a-single-job
func (s *JobsService) GetJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// GetJobArtifacts get jobs artifacts of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#get-job-artifacts
func (s *JobsService) GetJobArtifacts(pid interface{}, jobID int, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	artifactsBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactsBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactsBuf, resp, err
}

// DownloadArtifactsFileOptions represents the available DownloadArtifactsFile()
// options.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-the-artifacts-archive
type DownloadArtifactsFileOptions struct {
	Job *string `url:"job" json:"job"`
}

// DownloadArtifactsFile download the artifacts file from the given
// reference name and job provided the job finished successfully.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-the-artifacts-archive
func (s *JobsService) DownloadArtifactsFile(pid interface{}, refName string, opt *DownloadArtifactsFileOptions, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/artifacts/%s/download", pathEscape(project), refName)

	req, err := s.client.NewRequest("GET", u, opt, options)
	if err != nil {
		return nil, nil, err
	}

	artifactsBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactsBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactsBuf, resp, err
}

// DownloadSingleArtifactsFile download a file from the artifacts from the
// given reference name and job provided the job finished successfully.
// Only a single file is going to be extracted from the archive and streamed
// to a client.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#download-a-single-artifact-file-by-job-id
func (s *JobsService) DownloadSingleArtifactsFile(pid interface{}, jobID int, artifactPath string, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}

	u := fmt.Sprintf(
		"projects/%s/jobs/%d/artifacts/%s",
		pathEscape(project),
		jobID,
		artifactPath,
	)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	artifactBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, artifactBuf)
	if err != nil {
		return nil, resp, err
	}

	return artifactBuf, resp, err
}

// GetTraceFile gets a trace of a specific job of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#get-a-trace-file
func (s *JobsService) GetTraceFile(pid interface{}, jobID int, options ...RequestOptionFunc) (io.Reader, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/trace", pathEscape(project), jobID)

	req, err := s.client.NewRequest("GET", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	traceBuf := new(bytes.Buffer)
	resp, err := s.client.Do(req, traceBuf)
	if err != nil {
		return nil, resp, err
	}

	return traceBuf, resp, err
}

// CancelJob cancels a single job of a project.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#cancel-a-job
func (s *JobsService) CancelJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/cancel", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// RetryJob retries a single job of a project
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#retry-a-job
func (s *JobsService) RetryJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/retry", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// EraseJob erases a single job of a project, removes a job
// artifacts and a job trace.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#erase-a-job
func (s *JobsService) EraseJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/erase", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// KeepArtifacts prevents artifacts from being deleted when
// expiration is set.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#keep-artifacts
func (s *JobsService) KeepArtifacts(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts/keep", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// PlayJob triggers a manual action to start a job.
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/jobs.html#play-a-job
func (s *JobsService) PlayJob(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/play", pathEscape(project), jobID)

	req, err := s.client.NewRequest("POST", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}

// DeleteArtifacts delete artifacts of a job
//
// GitLab API docs:
// https://docs.gitlab.com/ce/api/job_artifacts.html#delete-artifacts
func (s *JobsService) DeleteArtifacts(pid interface{}, jobID int, options ...RequestOptionFunc) (*Job, *Response, error) {
	project, err := parseID(pid)
	if err != nil {
		return nil, nil, err
	}
	u := fmt.Sprintf("projects/%s/jobs/%d/artifacts", pathEscape(project), jobID)

	req, err := s.client.NewRequest("DELETE", u, nil, options)
	if err != nil {
		return nil, nil, err
	}

	job := new(Job)
	resp, err := s.client.Do(req, job)
	if err != nil {
		return nil, resp, err
	}

	return job, resp, err
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Knative 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 generators

import (
	"io"
	"k8s.io/gengo/generator"
	"k8s.io/gengo/namer"
	"k8s.io/gengo/types"
	"k8s.io/klog"
)

// reconcilerControllerGenerator produces a file for setting up the reconciler
// with injection.
type reconcilerControllerGenerator struct {
	generator.DefaultGen
	outputPackage string
	imports       namer.ImportTracker
	filtered      bool

	clientPkg           string
	schemePkg           string
	informerPackagePath string
}

var _ generator.Generator = (*reconcilerControllerGenerator)(nil)

func (g *reconcilerControllerGenerator) Filter(c *generator.Context, t *types.Type) bool {
	// We generate a single client, so return true once.
	if !g.filtered {
		g.filtered = true
		return true
	}
	return false
}

func (g *reconcilerControllerGenerator) Namers(c *generator.Context) namer.NameSystems {
	return namer.NameSystems{
		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
	}
}

func (g *reconcilerControllerGenerator) Imports(c *generator.Context) (imports []string) {
	imports = append(imports, g.imports.ImportLines()...)
	return
}

func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
	sw := generator.NewSnippetWriter(w, c, "{{", "}}")

	klog.V(5).Infof("processing type %v", t)

	m := map[string]interface{}{
		"type": t,
		"controllerImpl": c.Universe.Type(types.Name{
			Package: "knative.dev/pkg/controller",
			Name:    "Impl",
		}),
		"loggingFromContext": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/logging",
			Name:    "FromContext",
		}),
		"corev1EventSource": c.Universe.Function(types.Name{
			Package: "k8s.io/api/core/v1",
			Name:    "EventSource",
		}),
		"clientGet": c.Universe.Function(types.Name{
			Package: g.clientPkg,
			Name:    "Get",
		}),
		"informerGet": c.Universe.Function(types.Name{
			Package: g.informerPackagePath,
			Name:    "Get",
		}),
		"schemeScheme": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/kubernetes/scheme",
			Name:    "Scheme",
		}),
		"schemeAddToScheme": c.Universe.Function(types.Name{
			Package: g.schemePkg,
			Name:    "AddToScheme",
		}),
		"kubeclientGet": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/client/injection/kube/client",
			Name:    "Get",
		}),
		"typedcorev1EventSinkImpl": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/kubernetes/typed/core/v1",
			Name:    "EventSinkImpl",
		}),
		"recordNewBroadcaster": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/tools/record",
			Name:    "NewBroadcaster",
		}),
		"watchInterface": c.Universe.Type(types.Name{
			Package: "k8s.io/apimachinery/pkg/watch",
			Name:    "Interface",
		}),
		"controllerGetEventRecorder": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/controller",
			Name:    "GetEventRecorder",
		}),
	}

	sw.Do(reconcilerControllerNewImpl, m)

	return sw.Error()
}

var reconcilerControllerNewImpl = `
const (
	defaultControllerAgentName = "{{.type|lowercaseSingular}}-controller"
	defaultFinalizerName       = "{{.type|lowercaseSingular}}"
)

func NewImpl(ctx context.Context, r Interface) *{{.controllerImpl|raw}} {
	logger := {{.loggingFromContext|raw}}(ctx)

	{{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx)

	recorder := {{.controllerGetEventRecorder|raw}}(ctx)
	if recorder == nil {
		// Create event broadcaster
		logger.Debug("Creating event broadcaster")
		eventBroadcaster := {{.recordNewBroadcaster|raw}}()
		watches := []{{.watchInterface|raw}}{
			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
			eventBroadcaster.StartRecordingToSink(
				&{{.typedcorev1EventSinkImpl|raw}}{Interface: {{.kubeclientGet|raw}}(ctx).CoreV1().Events("")}),
		}
		recorder = eventBroadcaster.NewRecorder({{.schemeScheme|raw}}, {{.corev1EventSource|raw}}{Component: defaultControllerAgentName})
		go func() {
			<-ctx.Done()
			for _, w := range watches {
				w.Stop()
			}
		}()
	}

	c := &reconcilerImpl{
		Client:  {{.clientGet|raw}}(ctx),
		Lister:  {{.type|lowercaseSingular}}Informer.Lister(),
		Recorder: recorder,
		FinalizerName: defaultFinalizerName,
		reconciler:    r,
	}
	impl := controller.NewImpl(c, logger, "{{.type|allLowercasePlural}}")

	return impl
}

func init() {
	{{.schemeAddToScheme|raw}}({{.schemeScheme|raw}})
}
`
<commit_msg>golang format tools (#1056)<commit_after>/*
Copyright 2020 The Knative 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 generators

import (
	"io"

	"k8s.io/gengo/generator"
	"k8s.io/gengo/namer"
	"k8s.io/gengo/types"
	"k8s.io/klog"
)

// reconcilerControllerGenerator produces a file for setting up the reconciler
// with injection.
type reconcilerControllerGenerator struct {
	generator.DefaultGen
	outputPackage string
	imports       namer.ImportTracker
	filtered      bool

	clientPkg           string
	schemePkg           string
	informerPackagePath string
}

var _ generator.Generator = (*reconcilerControllerGenerator)(nil)

func (g *reconcilerControllerGenerator) Filter(c *generator.Context, t *types.Type) bool {
	// We generate a single client, so return true once.
	if !g.filtered {
		g.filtered = true
		return true
	}
	return false
}

func (g *reconcilerControllerGenerator) Namers(c *generator.Context) namer.NameSystems {
	return namer.NameSystems{
		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
	}
}

func (g *reconcilerControllerGenerator) Imports(c *generator.Context) (imports []string) {
	imports = append(imports, g.imports.ImportLines()...)
	return
}

func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
	sw := generator.NewSnippetWriter(w, c, "{{", "}}")

	klog.V(5).Infof("processing type %v", t)

	m := map[string]interface{}{
		"type": t,
		"controllerImpl": c.Universe.Type(types.Name{
			Package: "knative.dev/pkg/controller",
			Name:    "Impl",
		}),
		"loggingFromContext": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/logging",
			Name:    "FromContext",
		}),
		"corev1EventSource": c.Universe.Function(types.Name{
			Package: "k8s.io/api/core/v1",
			Name:    "EventSource",
		}),
		"clientGet": c.Universe.Function(types.Name{
			Package: g.clientPkg,
			Name:    "Get",
		}),
		"informerGet": c.Universe.Function(types.Name{
			Package: g.informerPackagePath,
			Name:    "Get",
		}),
		"schemeScheme": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/kubernetes/scheme",
			Name:    "Scheme",
		}),
		"schemeAddToScheme": c.Universe.Function(types.Name{
			Package: g.schemePkg,
			Name:    "AddToScheme",
		}),
		"kubeclientGet": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/client/injection/kube/client",
			Name:    "Get",
		}),
		"typedcorev1EventSinkImpl": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/kubernetes/typed/core/v1",
			Name:    "EventSinkImpl",
		}),
		"recordNewBroadcaster": c.Universe.Function(types.Name{
			Package: "k8s.io/client-go/tools/record",
			Name:    "NewBroadcaster",
		}),
		"watchInterface": c.Universe.Type(types.Name{
			Package: "k8s.io/apimachinery/pkg/watch",
			Name:    "Interface",
		}),
		"controllerGetEventRecorder": c.Universe.Function(types.Name{
			Package: "knative.dev/pkg/controller",
			Name:    "GetEventRecorder",
		}),
	}

	sw.Do(reconcilerControllerNewImpl, m)

	return sw.Error()
}

var reconcilerControllerNewImpl = `
const (
	defaultControllerAgentName = "{{.type|lowercaseSingular}}-controller"
	defaultFinalizerName       = "{{.type|lowercaseSingular}}"
)

func NewImpl(ctx context.Context, r Interface) *{{.controllerImpl|raw}} {
	logger := {{.loggingFromContext|raw}}(ctx)

	{{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx)

	recorder := {{.controllerGetEventRecorder|raw}}(ctx)
	if recorder == nil {
		// Create event broadcaster
		logger.Debug("Creating event broadcaster")
		eventBroadcaster := {{.recordNewBroadcaster|raw}}()
		watches := []{{.watchInterface|raw}}{
			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
			eventBroadcaster.StartRecordingToSink(
				&{{.typedcorev1EventSinkImpl|raw}}{Interface: {{.kubeclientGet|raw}}(ctx).CoreV1().Events("")}),
		}
		recorder = eventBroadcaster.NewRecorder({{.schemeScheme|raw}}, {{.corev1EventSource|raw}}{Component: defaultControllerAgentName})
		go func() {
			<-ctx.Done()
			for _, w := range watches {
				w.Stop()
			}
		}()
	}

	c := &reconcilerImpl{
		Client:  {{.clientGet|raw}}(ctx),
		Lister:  {{.type|lowercaseSingular}}Informer.Lister(),
		Recorder: recorder,
		FinalizerName: defaultFinalizerName,
		reconciler:    r,
	}
	impl := controller.NewImpl(c, logger, "{{.type|allLowercasePlural}}")

	return impl
}

func init() {
	{{.schemeAddToScheme|raw}}({{.schemeScheme|raw}})
}
`
<|endoftext|>


<commit_before>// Copyright 2016, Marc Lavergne <mlavergn@gmail.com>. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package goweb

import (
	"encoding/json"
	"errors"
	"fmt"
	. "golog"
	"strconv"
	"strings"
)

type JSONSliceType []interface{}
type JSONMapType map[string]interface{}

const (
	JSONArrayType = iota
	JSONDictionaryType
	JSONUnknownType
)

type _JSONDelimiter []string

var _JSONArrayDelimiter = []string{"[", "]"}
var _JSONDictionaryDelimiter = []string{"{", "}"}

//
// IdentifityJSONFragment
//
func IdentifityJSONFragment(jsonString string) (result int, index int) {
	result = JSONUnknownType
	index = -1

	arrDelimiterIndex := -1
	dictDelimiterIndex := strings.Index(jsonString, _JSONDictionaryDelimiter[0])
	if dictDelimiterIndex == 0 {
		result = JSONDictionaryType
		index = dictDelimiterIndex
	} else {
		arrDelimiterIndex = strings.Index(jsonString, _JSONArrayDelimiter[0])
		if dictDelimiterIndex == -1 && arrDelimiterIndex >= 0 {
			result = JSONArrayType
			index = arrDelimiterIndex
		} else if arrDelimiterIndex == -1 && dictDelimiterIndex >= 0 {
			result = JSONDictionaryType
			index = dictDelimiterIndex
		} else if dictDelimiterIndex < arrDelimiterIndex {
			result = JSONDictionaryType
			index = dictDelimiterIndex
		} else if arrDelimiterIndex < dictDelimiterIndex {
			result = JSONArrayType
			index = arrDelimiterIndex
		}
	}

	return
}

//
// ToJSON
//
func ToJSON(jsonMap JSONMapType) (result string, err error) {
	jsonBytes, err := json.Marshal(jsonMap)
	if err != nil {
		LogError(err)
	} else {
		result = string(jsonBytes)
	}

	return
}

//
// FromJSON
//
func FromJSON(jsonString string) (result JSONMapType, err error) {
	bytes := []byte(jsonString)
	err = json.Unmarshal(bytes, &result)

	if err != nil {
		if strings.Index(err.Error(), "cannot unmarshal array into") != -1 {
			var resultArr JSONSliceType
			err = json.Unmarshal(bytes, &resultArr)
			if err == nil {
				result = make(JSONMapType)
				result["[]"] = resultArr
			}
		}
	}

	return
}

//
// ExtractJSON isolates and tidys JSON string while attepting to parse the contents into a JSONMap
//
func ExtractJSON(jsonString string, jsonType int) (result JSONMapType, err error) {
	// unmarshall is strict and wants complete JSON structures
	jsonString, jsonType = IsolateJSON(jsonString, jsonType)
	result, err = FromJSON(jsonString)
	if err != nil {
		jsonString = TidyScript(jsonString)
		result, err = FromJSON(jsonString)
		if err != nil {
			jsonString = TidyJSON(jsonString, jsonType)
			result, err = FromJSON(jsonString)
		}
	}

	return
}

//
// IsolateJSON isolates the JSON parsable text based on array or dictionary delimiters
//
func IsolateJSON(jsonString string, jsonTypeIn int) (result string, jsonType int) {
	var delimiter []string = nil
	var delimiterIndex int

	if jsonTypeIn == JSONUnknownType {
		jsonType, delimiterIndex = IdentifityJSONFragment(jsonString)
	} else {
		jsonType = jsonTypeIn
		delimiterIndex = strings.Index(jsonString, _JSONDictionaryDelimiter[0])

	}

	switch jsonType {
	case JSONArrayType:
		delimiter = _JSONArrayDelimiter
	case JSONDictionaryType:
		delimiter = _JSONDictionaryDelimiter
	default:
		return
	}

	if delimiterIndex > 0 {
		result = jsonString[delimiterIndex:]
	}

	delimiterIndex = strings.Index(result, delimiter[1])
	if delimiterIndex >= 0 {
		result = result[:delimiterIndex+1]
	}

	return
}

//
// TidyScript
//
func TidyScript(jsonString string) (result string) {
	// no newlines
	result = strings.Replace(jsonString, "\n", "", -1)
	// no tabs
	result = strings.Replace(result, "\t", "", -1)

	return
}

//
// TidyJSON
//
func TidyJSON(jsonString string, jsonType int) (result string) {
	// JSON improper escaping detected - need to split the string and tidy it
	var jsonDelimiter []string
	if jsonType == JSONDictionaryType {
		// dictionary cleanup
		jsonDelimiter = _JSONDictionaryDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			val := strings.Split(entry, ":")
			result += fmt.Sprintf("\"%s\": \"%s\",", strings.Trim(val[0], " '\""), strings.Trim(val[1], " '\""))
		}
	} else {
		// array
		jsonDelimiter = _JSONArrayDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			result += fmt.Sprintf("\"%s\",", strings.Trim(entry, " '"))
		}
	}

	// reconstitute the result dropping the last comma
	result = jsonDelimiter[0] + result[:len(result)-1] + jsonDelimiter[1]

	return result
}

func numericConversion(jsonstring string) (result int, err error) {
	if strings.ToLower(jsonstring) == strings.ToUpper(jsonstring) {
		result, err = strconv.Atoi(jsonstring)
		if err != nil {
			result, err = EvaluateEquation(jsonstring)
		}
	} else {
		err = errors.New("string")
	}

	return
}

//
// TidyValues
//
func TidyValues(jsonString string, jsonType int) (result string) {
	var jsonDelimiter []string
	if jsonType == JSONDictionaryType {
		// dictionary cleanup
		jsonDelimiter = _JSONDictionaryDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			val := strings.Split(entry, ":")
			ival, err := numericConversion(val[1])
			if err == nil {
				result += fmt.Sprintf("\"%s\": %d,", val[0], ival)
			} else {
				result += fmt.Sprintf("\"%s\": \"%s\",", val[0], val[1])
			}
		}
	} else {
		// array
		jsonDelimiter = _JSONArrayDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			ival, err := numericConversion(entry)
			if err == nil {
				result += fmt.Sprintf("%d,", ival)
			} else {
				result += fmt.Sprintf("\"%s\",", entry)
			}
		}
	}

	// reconstitute the result dropping the last comma
	result = jsonDelimiter[0] + result[:len(result)-1] + jsonDelimiter[1]

	return result
}
<commit_msg>Fix: JSON index checks<commit_after>// Copyright 2016, Marc Lavergne <mlavergn@gmail.com>. All rights reserved.
// Use of this source code is governed by the MIT
// license that can be found in the LICENSE file.

package goweb

import (
	"encoding/json"
	"errors"
	"fmt"
	. "golog"
	"strconv"
	"strings"
)

type JSONSliceType []interface{}
type JSONMapType map[string]interface{}

const (
	JSONArrayType = iota
	JSONDictionaryType
	JSONUnknownType
)

type _JSONDelimiter []string

var _JSONArrayDelimiter = []string{"[", "]"}
var _JSONDictionaryDelimiter = []string{"{", "}"}

//
// IdentifityJSONFragment
//
func IdentifityJSONFragment(jsonString string) (result int, index int) {
	result = JSONUnknownType
	index = -1

	arrDelimiterIndex := -1
	dictDelimiterIndex := strings.Index(jsonString, _JSONDictionaryDelimiter[0])
	if dictDelimiterIndex == 0 {
		result = JSONDictionaryType
		index = dictDelimiterIndex
	} else {
		arrDelimiterIndex = strings.Index(jsonString, _JSONArrayDelimiter[0])
		if dictDelimiterIndex == -1 && arrDelimiterIndex >= 0 {
			result = JSONArrayType
			index = arrDelimiterIndex
		} else if arrDelimiterIndex == -1 && dictDelimiterIndex >= 0 {
			result = JSONDictionaryType
			index = dictDelimiterIndex
		} else if dictDelimiterIndex < arrDelimiterIndex {
			result = JSONDictionaryType
			index = dictDelimiterIndex
		} else if arrDelimiterIndex < dictDelimiterIndex {
			result = JSONArrayType
			index = arrDelimiterIndex
		}
	}

	return
}

//
// ToJSON
//
func ToJSON(jsonMap JSONMapType) (result string, err error) {
	jsonBytes, err := json.Marshal(jsonMap)
	if err != nil {
		LogError(err)
	} else {
		result = string(jsonBytes)
	}

	return
}

//
// FromJSON
//
func FromJSON(jsonString string) (result JSONMapType, err error) {
	bytes := []byte(jsonString)
	err = json.Unmarshal(bytes, &result)

	if err != nil {
		if strings.Index(err.Error(), "cannot unmarshal array into") != -1 {
			var resultArr JSONSliceType
			err = json.Unmarshal(bytes, &resultArr)
			if err == nil {
				result = make(JSONMapType)
				result["[]"] = resultArr
			}
		}
	}

	return
}

//
// ExtractJSON isolates and tidys JSON string while attepting to parse the contents into a JSONMap
//
func ExtractJSON(jsonString string, jsonType int) (result JSONMapType, err error) {
	// unmarshall is strict and wants complete JSON structures
	jsonString, jsonType = IsolateJSON(jsonString, jsonType)
	result, err = FromJSON(jsonString)
	if err != nil {
		jsonString = TidyScript(jsonString)
		result, err = FromJSON(jsonString)
		if err != nil {
			jsonString = TidyJSON(jsonString, jsonType)
			result, err = FromJSON(jsonString)
		}
	}

	return
}

//
// IsolateJSON isolates the JSON parsable text based on array or dictionary delimiters
//
func IsolateJSON(jsonString string, jsonTypeIn int) (result string, jsonType int) {
	var delimiter []string = nil
	delimiterIndex := -1

	if jsonTypeIn == JSONUnknownType {
		jsonType, delimiterIndex = IdentifityJSONFragment(jsonString)
	} else {
		jsonType = jsonTypeIn
		delimiterIndex = strings.Index(jsonString, _JSONDictionaryDelimiter[0])

	}

	switch jsonType {
	case JSONArrayType:
		delimiter = _JSONArrayDelimiter
	case JSONDictionaryType:
		delimiter = _JSONDictionaryDelimiter
	default:
		return
	}

	if delimiterIndex >= 0 {
		result = jsonString[delimiterIndex:]
		delimiterIndex = strings.Index(result, delimiter[1])
		if delimiterIndex >= 0 {
			result = result[:delimiterIndex+1]
		}
	}

	return
}

//
// TidyScript
//
func TidyScript(jsonString string) (result string) {
	// no newlines
	result = strings.Replace(jsonString, "\n", "", -1)
	// no tabs
	result = strings.Replace(result, "\t", "", -1)

	return
}

//
// TidyJSON
//
func TidyJSON(jsonString string, jsonType int) (result string) {
	// JSON improper escaping detected - need to split the string and tidy it
	if len(jsonString) > 1 {
		var jsonDelimiter []string
		if jsonType == JSONDictionaryType {
			// dictionary cleanup
			jsonDelimiter = _JSONDictionaryDelimiter
			entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
			for _, entry := range entries {
				val := strings.Split(entry, ":")
				if len(val) >= 2 {
					result += fmt.Sprintf("\"%s\": \"%s\",", strings.Trim(val[0], " '\""), strings.Trim(val[1], " '\""))
				}
			}
		} else {
			// array
			jsonDelimiter = _JSONArrayDelimiter
			entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
			for _, entry := range entries {
				result += fmt.Sprintf("\"%s\",", strings.Trim(entry, " '"))
			}
		}

		// reconstitute the result dropping the last comma
		resultLen := len(result)
		if resultLen > 1 {
			result = jsonDelimiter[0] + result[:resultLen-1] + jsonDelimiter[1]
		}
	}

	return result
}

func numericConversion(jsonstring string) (result int, err error) {
	if strings.ToLower(jsonstring) == strings.ToUpper(jsonstring) {
		result, err = strconv.Atoi(jsonstring)
		if err != nil {
			result, err = EvaluateEquation(jsonstring)
		}
	} else {
		err = errors.New("string")
	}

	return
}

//
// TidyValues
//
func TidyValues(jsonString string, jsonType int) (result string) {
	var jsonDelimiter []string
	if jsonType == JSONDictionaryType {
		// dictionary cleanup
		jsonDelimiter = _JSONDictionaryDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			val := strings.Split(entry, ":")
			ival, err := numericConversion(val[1])
			if err == nil {
				result += fmt.Sprintf("\"%s\": %d,", val[0], ival)
			} else {
				result += fmt.Sprintf("\"%s\": \"%s\",", val[0], val[1])
			}
		}
	} else {
		// array
		jsonDelimiter = _JSONArrayDelimiter
		entries := strings.Split(jsonString[1:len(jsonString)-1], ",")
		for _, entry := range entries {
			ival, err := numericConversion(entry)
			if err == nil {
				result += fmt.Sprintf("%d,", ival)
			} else {
				result += fmt.Sprintf("\"%s\",", entry)
			}
		}
	}

	// reconstitute the result dropping the last comma
	result = jsonDelimiter[0] + result[:len(result)-1] + jsonDelimiter[1]

	return result
}
<|endoftext|>


<commit_before>package split

import "net/http"

// WriteResponses serialize the responses passed as argument into the ResponseWriter
func WriteResponses(w http.ResponseWriter, esponses []*http.Response) error {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Not impelmented yet"))
	return nil
}
<commit_msg>Collect and pack responses<commit_after>package split

import (
	"bytes"
	"mime"
	"mime/multipart"
	"net/http"
	"net/textproto"
)

// WriteResponses serialize the responses passed as argument into the ResponseWriter
func WriteResponses(w http.ResponseWriter, responses []*http.Response) error {
	var buf bytes.Buffer
	multipartWriter := multipart.NewWriter(&buf)

	mimeHeaders := textproto.MIMEHeader(make(map[string][]string))
	mimeHeaders.Set("Content-Type", "application/http")

	for _, resp := range responses {
		part, err := multipartWriter.CreatePart(mimeHeaders)
		if err != nil {
			return err
		}
		resp.Write(part)
	}

	w.WriteHeader(http.StatusOK)
	w.Header().Set("Content-Type", mime.FormatMediaType("multipart/mixed", map[string]string{"boundary": multipartWriter.Boundary()}))
	buf.WriteTo(w)
	return nil
}
<|endoftext|>


<commit_before>/* Static hash table test cases. */
package chunkfile

import (
	"os"
	"testing"
)

func TestPutGetReopenClear(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
	}
	// Test initial size information
	if !(ht.NumBuckets == INITIAL_BUCKETS && ht.File.UsedSize == INITIAL_BUCKETS*BUCKET_SIZE && ht.File.Size == HT_FILE_SIZE) {
		t.Fatal("Wrong size")
	}
	defer ht.File.Close()
	for i := uint64(0); i < 1024*1024*2; i++ {
		ht.Put(i, i)
	}
	for i := uint64(0); i < 1024*1024*2; i++ {
		keys, vals := ht.Get(i, 0, func(a, b uint64) bool {
			return true
		})
		if !(len(keys) == 1 && keys[0] == i && len(vals) == 1 && vals[0] == i) {
			t.Fatalf("Get failed on key %d, got %v and %v", i, keys, vals)
		}
	}
	numBuckets := ht.NumBuckets
	// Reopen the hash table and test the features
	reopened, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
	}
	if reopened.NumBuckets != numBuckets {
		t.Fatalf("Wrong NumBuckets")
	}
	if reopened.File.UsedSize != numBuckets*BUCKET_SIZE {
		t.Fatalf("Wrong UsedSize")
	}
	for i := uint64(0); i < 1024*1024*2; i++ {
		keys, vals := reopened.Get(i, 0, func(a, b uint64) bool {
			return true
		})
		if !(len(keys) == 1 && keys[0] == i && len(vals) == 1 && vals[0] == i) {
			t.Fatalf("Get failed on key %d, got %v and %v", i, keys, vals)
		}
	}
	// Clear the hash table
	reopened.Clear()
	if !(reopened.NumBuckets == INITIAL_BUCKETS && reopened.File.UsedSize == INITIAL_BUCKETS*BUCKET_SIZE) {
		t.Fatal("Did not clear the hash table")
	}
	keys, vals := ht.GetAll(0)
	if len(keys) != 0 || len(vals) != 0 {
		t.Fatal("Did not clear the hash table")
	}
}

func TestPutGet2(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	keys, vals := ht.Get(1, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 3 && len(vals) == 3) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
	keys, vals = ht.Get(2, 2, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
	keys, vals = ht.Get(1, 0, func(a, b uint64) bool {
		return b >= 2
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
}

func TestPutRemove(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	ht.Remove(1, 1)
	ht.Remove(2, 2)
	keys, vals := ht.Get(1, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Did not delete, still have %v, %v", keys, vals)
	}
	keys, vals = ht.Get(2, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Did not delete, still have %v, %v", keys, vals)
	}
}

func TestGetAll(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	keys, vals := ht.GetAll(0)
	if !(len(keys) == 16 && len(vals) == 16) {
		t.Fatalf("Did not get everything, got only %v, %v", keys, vals)
	}
	keys, vals = ht.GetAll(3)
	if !(len(keys) == 3 && len(vals) == 3) {
		t.Fatalf("Did not get three values, got %v, %v", keys, vals)
	}
}
<commit_msg>just a reminder<commit_after>/* Static hash table test cases. */
package chunkfile

import (
	"fmt"
	"os"
	"testing"
)

func TestPutGetReopenClear(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
	}
	// Test initial size information
	if !(ht.NumBuckets == INITIAL_BUCKETS && ht.File.UsedSize == INITIAL_BUCKETS*BUCKET_SIZE && ht.File.Size == HT_FILE_SIZE) {
		t.Fatal("Wrong size")
	}
	defer ht.File.Close()
	fmt.Println("Please be patient, this may take a minute.")
	for i := uint64(0); i < 1024*1024*2; i++ {
		ht.Put(i, i)
	}
	for i := uint64(0); i < 1024*1024*2; i++ {
		keys, vals := ht.Get(i, 0, func(a, b uint64) bool {
			return true
		})
		if !(len(keys) == 1 && keys[0] == i && len(vals) == 1 && vals[0] == i) {
			t.Fatalf("Get failed on key %d, got %v and %v", i, keys, vals)
		}
	}
	numBuckets := ht.NumBuckets
	// Reopen the hash table and test the features
	reopened, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
	}
	if reopened.NumBuckets != numBuckets {
		t.Fatalf("Wrong NumBuckets")
	}
	if reopened.File.UsedSize != numBuckets*BUCKET_SIZE {
		t.Fatalf("Wrong UsedSize")
	}
	for i := uint64(0); i < 1024*1024*2; i++ {
		keys, vals := reopened.Get(i, 0, func(a, b uint64) bool {
			return true
		})
		if !(len(keys) == 1 && keys[0] == i && len(vals) == 1 && vals[0] == i) {
			t.Fatalf("Get failed on key %d, got %v and %v", i, keys, vals)
		}
	}
	// Clear the hash table
	reopened.Clear()
	if !(reopened.NumBuckets == INITIAL_BUCKETS && reopened.File.UsedSize == INITIAL_BUCKETS*BUCKET_SIZE) {
		t.Fatal("Did not clear the hash table")
	}
	keys, vals := ht.GetAll(0)
	if len(keys) != 0 || len(vals) != 0 {
		t.Fatal("Did not clear the hash table")
	}
}

func TestPutGet2(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	keys, vals := ht.Get(1, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 3 && len(vals) == 3) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
	keys, vals = ht.Get(2, 2, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
	keys, vals = ht.Get(1, 0, func(a, b uint64) bool {
		return b >= 2
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Get failed, got %v, %v", keys, vals)
	}
}

func TestPutRemove(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	ht.Remove(1, 1)
	ht.Remove(2, 2)
	keys, vals := ht.Get(1, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Did not delete, still have %v, %v", keys, vals)
	}
	keys, vals = ht.Get(2, 0, func(a, b uint64) bool {
		return true
	})
	if !(len(keys) == 2 && len(vals) == 2) {
		t.Fatalf("Did not delete, still have %v, %v", keys, vals)
	}
}

func TestGetAll(t *testing.T) {
	tmp := "/tmp/tiedot_hash_test"
	os.Remove(tmp)
	defer os.Remove(tmp)
	ht, err := OpenHash(tmp)
	if err != nil {
		t.Fatalf("Failed to open: %v", err)
		return
	}
	defer ht.File.Close()
	ht.Put(1, 1)
	ht.Put(1, 2)
	ht.Put(1, 3)
	ht.Put(2, 1)
	ht.Put(2, 2)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	ht.Put(2, 3)
	keys, vals := ht.GetAll(0)
	if !(len(keys) == 16 && len(vals) == 16) {
		t.Fatalf("Did not get everything, got only %v, %v", keys, vals)
	}
	keys, vals = ht.GetAll(3)
	if !(len(keys) == 3 && len(vals) == 3) {
		t.Fatalf("Did not get three values, got %v, %v", keys, vals)
	}
}
<|endoftext|>


<commit_before>package has_vm_test

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	testhelperscpi "github.com/maximilien/bosh-softlayer-cpi/test_helpers"
	slclient "github.com/maximilien/softlayer-go/client"
	datatypes "github.com/maximilien/softlayer-go/data_types"
	softlayer "github.com/maximilien/softlayer-go/softlayer"
	testhelpers "github.com/maximilien/softlayer-go/test_helpers"
)

const configPath = "test_fixtures/cpi_methods/config.json"

var _ = Describe("BOSH Director Level Integration for has_vm", func() {
	var (
		err error

		client softlayer.Client

		username, apiKey string

		accountService      softlayer.SoftLayer_Account_Service
		virtualGuestService softlayer.SoftLayer_Virtual_Guest_Service

		virtualGuest  datatypes.SoftLayer_Virtual_Guest
		createdSshKey datatypes.SoftLayer_Security_Ssh_Key

		rootTemplatePath, tmpConfigPath, strVGID string

		replacementMap map[string]string

		output map[string]interface{}
	)

	BeforeEach(func() {
		username = os.Getenv("SL_USERNAME")
		Expect(username).ToNot(Equal(""), "username cannot be empty, set SL_USERNAME")

		apiKey = os.Getenv("SL_API_KEY")
		Expect(apiKey).ToNot(Equal(""), "apiKey cannot be empty, set SL_API_KEY")

		client = slclient.NewSoftLayerClient(username, apiKey)
		Expect(client).ToNot(BeNil())

		accountService, err = testhelpers.CreateAccountService()
		Expect(err).ToNot(HaveOccurred())

		virtualGuestService, err = testhelpers.CreateVirtualGuestService()
		Expect(err).ToNot(HaveOccurred())

		testhelpers.TIMEOUT = 35 * time.Minute
		testhelpers.POLLING_INTERVAL = 10 * time.Second

		pwd, err := os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		rootTemplatePath = filepath.Join(pwd, "..", "..")

		tmpConfigPath, err = testhelperscpi.CreateTmpConfigPath(rootTemplatePath, configPath, username, apiKey)
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		err = os.RemoveAll(tmpConfigPath)
		Expect(err).ToNot(HaveOccurred())
	})

	Context("has_vm with actual vm", func() {
		BeforeEach(func() {
			err = testhelpers.FindAndDeleteTestSshKeys()
			Expect(err).ToNot(HaveOccurred())

			createdSshKey, _ = testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)

			virtualGuest = testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{createdSshKey})

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			strVGID = strconv.Itoa(virtualGuest.Id)

			replacementMap = map[string]string{
				"ID":           strVGID,
				"DirectorUuid": "fake-director-uuid",
			}
		})

		AfterEach(func() {
			testhelpers.DeleteVirtualGuest(virtualGuest.Id)
			testhelpers.DeleteSshKey(createdSshKey.Id)
		})

		It("returns true because vm exists", func() {
			jsonPayload, err := testhelperscpi.GenerateCpiJsonPayload("has_vm", rootTemplatePath, replacementMap)
			Expect(err).ToNot(HaveOccurred())

			outputBytes, err := testhelperscpi.RunCpi(rootTemplatePath, tmpConfigPath, jsonPayload)
			Expect(err).ToNot(HaveOccurred())

			err = json.Unmarshal(outputBytes, &output)
			Expect(err).ToNot(HaveOccurred())
			Expect(output["result"]).To(BeTrue())
		})
	})

	Context("has_vm without valid vm id", func() {
		BeforeEach(func() {
			replacementMap = map[string]string{
				"ID":           "123456",
				"DirectorUuid": "fake-director-uuid",
			}
		})

		It("returns false because vm doesn't exist", func() {
			jsonPayload, err := testhelperscpi.GenerateCpiJsonPayload("has_vm", rootTemplatePath, replacementMap)
			Expect(err).ToNot(HaveOccurred())

			outputBytes, err := testhelperscpi.RunCpi(rootTemplatePath, tmpConfigPath, jsonPayload)
			Expect(err).ToNot(HaveOccurred())

			err = json.Unmarshal(outputBytes, &output)
			Expect(err).ToNot(HaveOccurred())
			Expect(output["result"]).To(BeFalse())
		})
	})
})
<commit_msg>finally fixing this go fmt issue<commit_after>package has_vm_test

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	testhelperscpi "github.com/maximilien/bosh-softlayer-cpi/test_helpers"
	slclient "github.com/maximilien/softlayer-go/client"
	datatypes "github.com/maximilien/softlayer-go/data_types"
	softlayer "github.com/maximilien/softlayer-go/softlayer"
	testhelpers "github.com/maximilien/softlayer-go/test_helpers"
)

const configPath = "test_fixtures/cpi_methods/config.json"

var _ = Describe("BOSH Director Level Integration for has_vm", func() {
	var (
		err error

		client softlayer.Client

		username, apiKey string

		accountService      softlayer.SoftLayer_Account_Service
		virtualGuestService softlayer.SoftLayer_Virtual_Guest_Service

		virtualGuest  datatypes.SoftLayer_Virtual_Guest
		createdSshKey datatypes.SoftLayer_Security_Ssh_Key

		rootTemplatePath, tmpConfigPath, strVGID string

		replacementMap map[string]string

		output map[string]interface{}
	)

	BeforeEach(func() {
		username = os.Getenv("SL_USERNAME")
		Expect(username).ToNot(Equal(""), "username cannot be empty, set SL_USERNAME")

		apiKey = os.Getenv("SL_API_KEY")
		Expect(apiKey).ToNot(Equal(""), "apiKey cannot be empty, set SL_API_KEY")

		client = slclient.NewSoftLayerClient(username, apiKey)
		Expect(client).ToNot(BeNil())

		accountService, err = testhelpers.CreateAccountService()
		Expect(err).ToNot(HaveOccurred())

		virtualGuestService, err = testhelpers.CreateVirtualGuestService()
		Expect(err).ToNot(HaveOccurred())

		testhelpers.TIMEOUT = 35 * time.Minute
		testhelpers.POLLING_INTERVAL = 10 * time.Second

		pwd, err := os.Getwd()
		Expect(err).ToNot(HaveOccurred())
		rootTemplatePath = filepath.Join(pwd, "..", "..")

		tmpConfigPath, err = testhelperscpi.CreateTmpConfigPath(rootTemplatePath, configPath, username, apiKey)
		Expect(err).ToNot(HaveOccurred())
	})

	AfterEach(func() {
		err = os.RemoveAll(tmpConfigPath)
		Expect(err).ToNot(HaveOccurred())
	})

	Context("has_vm with actual vm", func() {
		BeforeEach(func() {
			err = testhelpers.FindAndDeleteTestSshKeys()
			Expect(err).ToNot(HaveOccurred())

			createdSshKey, _ = testhelpers.CreateTestSshKey()
			testhelpers.WaitForCreatedSshKeyToBePresent(createdSshKey.Id)

			virtualGuest = testhelpers.CreateVirtualGuestAndMarkItTest([]datatypes.SoftLayer_Security_Ssh_Key{createdSshKey})

			testhelpers.WaitForVirtualGuestToBeRunning(virtualGuest.Id)
			testhelpers.WaitForVirtualGuestToHaveNoActiveTransactions(virtualGuest.Id)

			strVGID = strconv.Itoa(virtualGuest.Id)

			replacementMap = map[string]string{
				"ID":           strVGID,
				"DirectorUuid": "fake-director-uuid",
			}
		})

		AfterEach(func() {
			testhelpers.DeleteVirtualGuest(virtualGuest.Id)
			testhelpers.DeleteSshKey(createdSshKey.Id)
		})

		It("returns true because vm exists", func() {
			jsonPayload, err := testhelperscpi.GenerateCpiJsonPayload("has_vm", rootTemplatePath, replacementMap)
			Expect(err).ToNot(HaveOccurred())

			outputBytes, err := testhelperscpi.RunCpi(rootTemplatePath, tmpConfigPath, jsonPayload)
			Expect(err).ToNot(HaveOccurred())

			err = json.Unmarshal(outputBytes, &output)
			Expect(err).ToNot(HaveOccurred())
			Expect(output["result"]).To(BeTrue())
		})
	})

	Context("has_vm without valid vm id", func() {
		BeforeEach(func() {
			replacementMap = map[string]string{
				"ID":           "123456",
				"DirectorUuid": "fake-director-uuid",
			}
		})

		It("returns false because vm doesn't exist", func() {
			jsonPayload, err := testhelperscpi.GenerateCpiJsonPayload("has_vm", rootTemplatePath, replacementMap)
			Expect(err).ToNot(HaveOccurred())

			outputBytes, err := testhelperscpi.RunCpi(rootTemplatePath, tmpConfigPath, jsonPayload)
			Expect(err).ToNot(HaveOccurred())

			err = json.Unmarshal(outputBytes, &output)
			Expect(err).ToNot(HaveOccurred())
			Expect(output["result"]).To(BeFalse())
		})
	})
})
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 symbolizer provides a routine to populate a profile with
// symbol, file and line number information. It relies on the
// addr2liner and demangle packages to do the actual work.
package symbolizer

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"

	"github.com/google/pprof/internal/binutils"
	"github.com/google/pprof/internal/plugin"
	"github.com/google/pprof/internal/symbolz"
	"github.com/google/pprof/profile"
	"github.com/ianlancetaylor/demangle"
)

// Symbolizer implements the plugin.Symbolize interface.
type Symbolizer struct {
	Obj       plugin.ObjTool
	UI        plugin.UI
	Transport http.RoundTripper
}

// test taps for dependency injection
var symbolzSymbolize = symbolz.Symbolize
var localSymbolize = doLocalSymbolize
var demangleFunction = Demangle

// Symbolize attempts to symbolize profile p. First uses binutils on
// local binaries; if the source is a URL it attempts to get any
// missed entries using symbolz.
func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {
	remote, local, fast, force, demanglerMode := true, true, false, false, ""
	for _, o := range strings.Split(strings.ToLower(mode), ":") {
		switch o {
		case "":
			continue
		case "none", "no":
			return nil
		case "local":
			remote, local = false, true
		case "fastlocal":
			remote, local, fast = false, true, true
		case "remote":
			remote, local = true, false
		case "force":
			force = true
		default:
			switch d := strings.TrimPrefix(o, "demangle="); d {
			case "full", "none", "templates":
				demanglerMode = d
				force = true
				continue
			case "default":
				continue
			}
			s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode)
			s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]")
		}
	}

	var err error
	if local {
		// Symbolize locally using binutils.
		if err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil {
			s.UI.PrintErr("local symbolization: " + err.Error())
		}
	}
	if remote {
		post := func(source, post string) ([]byte, error) {
			return postURL(source, post, s.Transport)
		}
		if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
			return err // Ran out of options.
		}
	}

	demangleFunction(p, force, demanglerMode)
	return nil
}

// postURL issues a POST to a URL over HTTP.
func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
	client := &http.Client{
		Transport: tr,
	}
	resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
	if err != nil {
		return nil, fmt.Errorf("http post %s: %v", source, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp))
	}
	return ioutil.ReadAll(resp.Body)
}

func statusCodeError(resp *http.Response) error {
	if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
		// error is from pprof endpoint
		if body, err := ioutil.ReadAll(resp.Body); err == nil {
			return fmt.Errorf("server response: %s - %s", resp.Status, body)
		}
	}
	return fmt.Errorf("server response: %s", resp.Status)
}

// doLocalSymbolize adds symbol and line number information to all locations
// in a profile. mode enables some options to control
// symbolization.
func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
	if fast {
		if bu, ok := obj.(*binutils.Binutils); ok {
			bu.SetFastSymbolization(true)
		}
	}

	mt, err := newMapping(prof, obj, ui, force)
	if err != nil {
		return err
	}
	defer mt.close()

	functions := make(map[profile.Function]*profile.Function)
	for _, l := range mt.prof.Location {
		m := l.Mapping
		segment := mt.segments[m]
		if segment == nil {
			// Nothing to do.
			continue
		}

		stack, err := segment.SourceLine(l.Address)
		if err != nil || len(stack) == 0 {
			// No answers from addr2line.
			continue
		}

		l.Line = make([]profile.Line, len(stack))
		l.IsFolded = false
		for i, frame := range stack {
			if frame.Func != "" {
				m.HasFunctions = true
			}
			if frame.File != "" {
				m.HasFilenames = true
			}
			if frame.Line != 0 {
				m.HasLineNumbers = true
			}
			f := &profile.Function{
				Name:       frame.Func,
				SystemName: frame.Func,
				Filename:   frame.File,
			}
			if fp := functions[*f]; fp != nil {
				f = fp
			} else {
				functions[*f] = f
				f.ID = uint64(len(mt.prof.Function)) + 1
				mt.prof.Function = append(mt.prof.Function, f)
			}
			l.Line[i] = profile.Line{
				Function: f,
				Line:     int64(frame.Line),
			}
		}

		if len(stack) > 0 {
			m.HasInlineFrames = true
		}
	}

	return nil
}

// Demangle updates the function names in a profile with demangled C++
// names, simplified according to demanglerMode. If force is set,
// overwrite any names that appear already demangled.
func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
	if force {
		// Remove the current demangled names to force demangling
		for _, f := range prof.Function {
			if f.Name != "" && f.SystemName != "" {
				f.Name = f.SystemName
			}
		}
	}

	var options []demangle.Option
	switch demanglerMode {
	case "": // demangled, simplified: no parameters, no templates, no return type
		options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
	case "templates": // demangled, simplified: no parameters, no return type
		options = []demangle.Option{demangle.NoParams}
	case "full":
		options = []demangle.Option{demangle.NoClones}
	case "none": // no demangling
		return
	}

	// Copy the options because they may be updated by the call.
	o := make([]demangle.Option, len(options))
	for _, fn := range prof.Function {
		if fn.Name != "" && fn.SystemName != fn.Name {
			continue // Already demangled.
		}
		copy(o, options)
		if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
			fn.Name = demangled
			continue
		}
		// Could not demangle. Apply heuristics in case the name is
		// already demangled.
		name := fn.SystemName
		if looksLikeDemangledCPlusPlus(name) {
			if demanglerMode == "" || demanglerMode == "templates" {
				name = removeMatching(name, '(', ')')
			}
			if demanglerMode == "" {
				name = removeMatching(name, '<', '>')
			}
		}
		fn.Name = name
	}
}

// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
// the result of demangling C++. If so, further heuristics will be
// applied to simplify the name.
func looksLikeDemangledCPlusPlus(demangled string) bool {
	if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
		return false
	}
	return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
}

// removeMatching removes nested instances of start..end from name.
func removeMatching(name string, start, end byte) string {
	s := string(start) + string(end)
	var nesting, first, current int
	for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
		switch current += index; name[current] {
		case start:
			nesting++
			if nesting == 1 {
				first = current
			}
		case end:
			nesting--
			switch {
			case nesting < 0:
				return name // Mismatch, abort
			case nesting == 0:
				name = name[:first] + name[current+1:]
				current = first - 1
			}
		}
		current++
	}
	return name
}

// newMapping creates a mappingTable for a profile.
func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
	mt := &mappingTable{
		prof:     prof,
		segments: make(map[*profile.Mapping]plugin.ObjFile),
	}

	// Identify used mappings
	mappings := make(map[*profile.Mapping]bool)
	for _, l := range prof.Location {
		mappings[l.Mapping] = true
	}

	missingBinaries := false
	for midx, m := range prof.Mapping {
		if !mappings[m] {
			continue
		}

		// Do not attempt to re-symbolize a mapping that has already been symbolized.
		if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
			continue
		}

		if m.File == "" {
			if midx == 0 {
				ui.PrintErr("Main binary filename not available.")
				continue
			}
			missingBinaries = true
			continue
		}

		// Skip well-known system mappings
		if m.Unsymbolizable() {
			continue
		}

		// Skip mappings pointing to a source URL
		if m.BuildID == "" {
			if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
				continue
			}
		}

		name := filepath.Base(m.File)
		f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
		if err != nil {
			ui.PrintErr("Local symbolization failed for ", name, ": ", err)
			missingBinaries = true
			continue
		}
		if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
			ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
			f.Close()
			continue
		}

		mt.segments[m] = f
	}
	if missingBinaries {
		ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
			"Try setting PPROF_BINARY_PATH to the search path for local binaries.")
	}
	return mt, nil
}

// mappingTable contains the mechanisms for symbolization of a
// profile.
type mappingTable struct {
	prof     *profile.Profile
	segments map[*profile.Mapping]plugin.ObjFile
}

// Close releases any external processes being used for the mapping.
func (mt *mappingTable) close() {
	for _, segment := range mt.segments {
		segment.Close()
	}
}
<commit_msg>Log build ID in local symbolization error messages. (#679)<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// 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 symbolizer provides a routine to populate a profile with
// symbol, file and line number information. It relies on the
// addr2liner and demangle packages to do the actual work.
package symbolizer

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"

	"github.com/google/pprof/internal/binutils"
	"github.com/google/pprof/internal/plugin"
	"github.com/google/pprof/internal/symbolz"
	"github.com/google/pprof/profile"
	"github.com/ianlancetaylor/demangle"
)

// Symbolizer implements the plugin.Symbolize interface.
type Symbolizer struct {
	Obj       plugin.ObjTool
	UI        plugin.UI
	Transport http.RoundTripper
}

// test taps for dependency injection
var symbolzSymbolize = symbolz.Symbolize
var localSymbolize = doLocalSymbolize
var demangleFunction = Demangle

// Symbolize attempts to symbolize profile p. First uses binutils on
// local binaries; if the source is a URL it attempts to get any
// missed entries using symbolz.
func (s *Symbolizer) Symbolize(mode string, sources plugin.MappingSources, p *profile.Profile) error {
	remote, local, fast, force, demanglerMode := true, true, false, false, ""
	for _, o := range strings.Split(strings.ToLower(mode), ":") {
		switch o {
		case "":
			continue
		case "none", "no":
			return nil
		case "local":
			remote, local = false, true
		case "fastlocal":
			remote, local, fast = false, true, true
		case "remote":
			remote, local = true, false
		case "force":
			force = true
		default:
			switch d := strings.TrimPrefix(o, "demangle="); d {
			case "full", "none", "templates":
				demanglerMode = d
				force = true
				continue
			case "default":
				continue
			}
			s.UI.PrintErr("ignoring unrecognized symbolization option: " + mode)
			s.UI.PrintErr("expecting -symbolize=[local|fastlocal|remote|none][:force][:demangle=[none|full|templates|default]")
		}
	}

	var err error
	if local {
		// Symbolize locally using binutils.
		if err = localSymbolize(p, fast, force, s.Obj, s.UI); err != nil {
			s.UI.PrintErr("local symbolization: " + err.Error())
		}
	}
	if remote {
		post := func(source, post string) ([]byte, error) {
			return postURL(source, post, s.Transport)
		}
		if err = symbolzSymbolize(p, force, sources, post, s.UI); err != nil {
			return err // Ran out of options.
		}
	}

	demangleFunction(p, force, demanglerMode)
	return nil
}

// postURL issues a POST to a URL over HTTP.
func postURL(source, post string, tr http.RoundTripper) ([]byte, error) {
	client := &http.Client{
		Transport: tr,
	}
	resp, err := client.Post(source, "application/octet-stream", strings.NewReader(post))
	if err != nil {
		return nil, fmt.Errorf("http post %s: %v", source, err)
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("http post %s: %v", source, statusCodeError(resp))
	}
	return ioutil.ReadAll(resp.Body)
}

func statusCodeError(resp *http.Response) error {
	if resp.Header.Get("X-Go-Pprof") != "" && strings.Contains(resp.Header.Get("Content-Type"), "text/plain") {
		// error is from pprof endpoint
		if body, err := ioutil.ReadAll(resp.Body); err == nil {
			return fmt.Errorf("server response: %s - %s", resp.Status, body)
		}
	}
	return fmt.Errorf("server response: %s", resp.Status)
}

// doLocalSymbolize adds symbol and line number information to all locations
// in a profile. mode enables some options to control
// symbolization.
func doLocalSymbolize(prof *profile.Profile, fast, force bool, obj plugin.ObjTool, ui plugin.UI) error {
	if fast {
		if bu, ok := obj.(*binutils.Binutils); ok {
			bu.SetFastSymbolization(true)
		}
	}

	mt, err := newMapping(prof, obj, ui, force)
	if err != nil {
		return err
	}
	defer mt.close()

	functions := make(map[profile.Function]*profile.Function)
	for _, l := range mt.prof.Location {
		m := l.Mapping
		segment := mt.segments[m]
		if segment == nil {
			// Nothing to do.
			continue
		}

		stack, err := segment.SourceLine(l.Address)
		if err != nil || len(stack) == 0 {
			// No answers from addr2line.
			continue
		}

		l.Line = make([]profile.Line, len(stack))
		l.IsFolded = false
		for i, frame := range stack {
			if frame.Func != "" {
				m.HasFunctions = true
			}
			if frame.File != "" {
				m.HasFilenames = true
			}
			if frame.Line != 0 {
				m.HasLineNumbers = true
			}
			f := &profile.Function{
				Name:       frame.Func,
				SystemName: frame.Func,
				Filename:   frame.File,
			}
			if fp := functions[*f]; fp != nil {
				f = fp
			} else {
				functions[*f] = f
				f.ID = uint64(len(mt.prof.Function)) + 1
				mt.prof.Function = append(mt.prof.Function, f)
			}
			l.Line[i] = profile.Line{
				Function: f,
				Line:     int64(frame.Line),
			}
		}

		if len(stack) > 0 {
			m.HasInlineFrames = true
		}
	}

	return nil
}

// Demangle updates the function names in a profile with demangled C++
// names, simplified according to demanglerMode. If force is set,
// overwrite any names that appear already demangled.
func Demangle(prof *profile.Profile, force bool, demanglerMode string) {
	if force {
		// Remove the current demangled names to force demangling
		for _, f := range prof.Function {
			if f.Name != "" && f.SystemName != "" {
				f.Name = f.SystemName
			}
		}
	}

	var options []demangle.Option
	switch demanglerMode {
	case "": // demangled, simplified: no parameters, no templates, no return type
		options = []demangle.Option{demangle.NoParams, demangle.NoTemplateParams}
	case "templates": // demangled, simplified: no parameters, no return type
		options = []demangle.Option{demangle.NoParams}
	case "full":
		options = []demangle.Option{demangle.NoClones}
	case "none": // no demangling
		return
	}

	// Copy the options because they may be updated by the call.
	o := make([]demangle.Option, len(options))
	for _, fn := range prof.Function {
		if fn.Name != "" && fn.SystemName != fn.Name {
			continue // Already demangled.
		}
		copy(o, options)
		if demangled := demangle.Filter(fn.SystemName, o...); demangled != fn.SystemName {
			fn.Name = demangled
			continue
		}
		// Could not demangle. Apply heuristics in case the name is
		// already demangled.
		name := fn.SystemName
		if looksLikeDemangledCPlusPlus(name) {
			if demanglerMode == "" || demanglerMode == "templates" {
				name = removeMatching(name, '(', ')')
			}
			if demanglerMode == "" {
				name = removeMatching(name, '<', '>')
			}
		}
		fn.Name = name
	}
}

// looksLikeDemangledCPlusPlus is a heuristic to decide if a name is
// the result of demangling C++. If so, further heuristics will be
// applied to simplify the name.
func looksLikeDemangledCPlusPlus(demangled string) bool {
	if strings.Contains(demangled, ".<") { // Skip java names of the form "class.<init>"
		return false
	}
	return strings.ContainsAny(demangled, "<>[]") || strings.Contains(demangled, "::")
}

// removeMatching removes nested instances of start..end from name.
func removeMatching(name string, start, end byte) string {
	s := string(start) + string(end)
	var nesting, first, current int
	for index := strings.IndexAny(name[current:], s); index != -1; index = strings.IndexAny(name[current:], s) {
		switch current += index; name[current] {
		case start:
			nesting++
			if nesting == 1 {
				first = current
			}
		case end:
			nesting--
			switch {
			case nesting < 0:
				return name // Mismatch, abort
			case nesting == 0:
				name = name[:first] + name[current+1:]
				current = first - 1
			}
		}
		current++
	}
	return name
}

// newMapping creates a mappingTable for a profile.
func newMapping(prof *profile.Profile, obj plugin.ObjTool, ui plugin.UI, force bool) (*mappingTable, error) {
	mt := &mappingTable{
		prof:     prof,
		segments: make(map[*profile.Mapping]plugin.ObjFile),
	}

	// Identify used mappings
	mappings := make(map[*profile.Mapping]bool)
	for _, l := range prof.Location {
		mappings[l.Mapping] = true
	}

	missingBinaries := false
	for midx, m := range prof.Mapping {
		if !mappings[m] {
			continue
		}

		// Do not attempt to re-symbolize a mapping that has already been symbolized.
		if !force && (m.HasFunctions || m.HasFilenames || m.HasLineNumbers) {
			continue
		}

		if m.File == "" {
			if midx == 0 {
				ui.PrintErr("Main binary filename not available.")
				continue
			}
			missingBinaries = true
			continue
		}

		// Skip well-known system mappings
		if m.Unsymbolizable() {
			continue
		}

		// Skip mappings pointing to a source URL
		if m.BuildID == "" {
			if u, err := url.Parse(m.File); err == nil && u.IsAbs() && strings.Contains(strings.ToLower(u.Scheme), "http") {
				continue
			}
		}

		name := filepath.Base(m.File)
		if m.BuildID != "" {
			name += fmt.Sprintf(" (build ID %s)", m.BuildID)
		}
		f, err := obj.Open(m.File, m.Start, m.Limit, m.Offset)
		if err != nil {
			ui.PrintErr("Local symbolization failed for ", name, ": ", err)
			missingBinaries = true
			continue
		}
		if fid := f.BuildID(); m.BuildID != "" && fid != "" && fid != m.BuildID {
			ui.PrintErr("Local symbolization failed for ", name, ": build ID mismatch")
			f.Close()
			continue
		}

		mt.segments[m] = f
	}
	if missingBinaries {
		ui.PrintErr("Some binary filenames not available. Symbolization may be incomplete.\n" +
			"Try setting PPROF_BINARY_PATH to the search path for local binaries.")
	}
	return mt, nil
}

// mappingTable contains the mechanisms for symbolization of a
// profile.
type mappingTable struct {
	prof     *profile.Profile
	segments map[*profile.Mapping]plugin.ObjFile
}

// Close releases any external processes being used for the mapping.
func (mt *mappingTable) close() {
	for _, segment := range mt.segments {
		segment.Close()
	}
}
<|endoftext|>


<commit_before>package filetype

import (
	"github.com/h2non/filetype/matchers"
	"github.com/h2non/filetype/types"
)

// Image tries to match a file as image type
func Image(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Image)
}

// IsImage checks if the given buffer is an image type
func IsImage(buf []byte) bool {
	kind, _ := Image(buf)
	return kind != types.Unknown
}

// Audio tries to match a file as audio type
func Audio(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Audio)
}

// IsAudio checks if the given buffer is an audio type
func IsAudio(buf []byte) bool {
	kind, _ := Audio(buf)
	return kind != types.Unknown
}

// Video tries to match a file as video type
func Video(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Video)
}

// IsVideo checks if the given buffer is a video type
func IsVideo(buf []byte) bool {
	kind, _ := Video(buf)
	return kind != types.Unknown
}

// Font tries to match a file as text font type
func Font(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Font)
}

// IsFont checks if the given buffer is a font type
func IsFont(buf []byte) bool {
	kind, _ := Font(buf)
	return kind != types.Unknown
}

// Archive tries to match a file as generic archive type
func Archive(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Archive)
}

// IsArchive checks if the given buffer is an archive type
func IsArchive(buf []byte) bool {
	kind, _ := Archive(buf)
	return kind != types.Unknown
}

// Document tries to match a file as document type
func Document(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Document)
}

// IsDocument checks if the given buffer is an document type
func IsDocument(buf []byte) bool {
	kind, _ := Document(buf)
	return kind != types.Unknown
}

func doMatchMap(buf []byte, machers matchers.Map) (types.Type, error) {
	kind := MatchMap(buf, machers)
	if kind != types.Unknown {
		return kind, nil
	}
	return kind, ErrUnknownBuffer
}
<commit_msg>fix(#108): add application file matchers<commit_after>package filetype

import (
	"github.com/h2non/filetype/matchers"
	"github.com/h2non/filetype/types"
)

// Image tries to match a file as image type
func Image(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Image)
}

// IsImage checks if the given buffer is an image type
func IsImage(buf []byte) bool {
	kind, _ := Image(buf)
	return kind != types.Unknown
}

// Audio tries to match a file as audio type
func Audio(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Audio)
}

// IsAudio checks if the given buffer is an audio type
func IsAudio(buf []byte) bool {
	kind, _ := Audio(buf)
	return kind != types.Unknown
}

// Video tries to match a file as video type
func Video(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Video)
}

// IsVideo checks if the given buffer is a video type
func IsVideo(buf []byte) bool {
	kind, _ := Video(buf)
	return kind != types.Unknown
}

// Font tries to match a file as text font type
func Font(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Font)
}

// IsFont checks if the given buffer is a font type
func IsFont(buf []byte) bool {
	kind, _ := Font(buf)
	return kind != types.Unknown
}

// Archive tries to match a file as generic archive type
func Archive(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Archive)
}

// IsArchive checks if the given buffer is an archive type
func IsArchive(buf []byte) bool {
	kind, _ := Archive(buf)
	return kind != types.Unknown
}

// Document tries to match a file as document type
func Document(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Document)
}

// IsDocument checks if the given buffer is an document type
func IsDocument(buf []byte) bool {
	kind, _ := Document(buf)
	return kind != types.Unknown
}

// Application tries to match a file as an application type
func Application(buf []byte) (types.Type, error) {
	return doMatchMap(buf, matchers.Application)
}

// IsApplication checks if the given buffer is an application type
func IsApplication(buf []byte) bool {
	kind, _ := Application(buf)
	return kind != types.Unknown
}

func doMatchMap(buf []byte, machers matchers.Map) (types.Type, error) {
	kind := MatchMap(buf, machers)
	if kind != types.Unknown {
		return kind, nil
	}
	return kind, ErrUnknownBuffer
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package loader

import (
	"github.com/issue9/web"

	"github.com/caixw/gitype/helper"
)

// 归档的类型
const (
	ArchiveTypeYear  = "year"
	ArchiveTypeMonth = "month"
)

// 归档的排序方式
const (
	ArchiveOrderDesc = "desc"
	ArchiveOrderAsc  = "asc"
)

// RSS RSS 和 Atom 相关的配置项
type RSS struct {
	Title string `yaml:"title"`
	URL   string `yaml:"url"`
	Type  string `yaml:"type,omitempty"`
	Size  int    `yaml:"size"` // 显示数量
}

// Opensearch opensearch 相关的配置
type Opensearch struct {
	URL   string `yaml:"url"`
	Type  string `yaml:"type,omitempty"`
	Title string `yaml:"title,omitempty"`

	ShortName   string `yaml:"shortName"`
	Description string `yaml:"description"`
	LongName    string `yaml:"longName,omitempty"`
	Image       *Icon  `yaml:"image,omitempty"`
}

// Sitemap sitemap 相关的配置
type Sitemap struct {
	URL  string `yaml:"url"`
	Type string `yaml:"type,omitempty"`

	XslURL     string  `yaml:"xslURL,omitempty"`    // 为 sitemap 指定一个 xsl 文件
	Priority   float64 `yaml:"priority"`            // 默认的优先级
	Changefreq string  `yaml:"changefreq"`          // 默认的更新频率
	EnableTag  bool    `yaml:"enableTag,omitempty"` // 是否将标签相关的页面写入 sitemap

	// 文章可以指定一个专门的值
	PostPriority   float64 `yaml:"postPriority"`
	PostChangefreq string  `yaml:"postChangefreq"`
}

// Archive 存档页的配置内容
type Archive struct {
	Order  string `yaml:"order"`            // 排序方式
	Type   string `yaml:"type,omitempty"`   // 存档的分类方式，可以按年或是按月
	Format string `yaml:"format,omitempty"` // 标题的格式化字符串
}

// Manifest 表示 PWA 中的相关配置
type Manifest struct {
	URL  string `yaml:"url"`
	Type string `yaml:"type,omitempty"`

	Lang        string  `yaml:"lang"`
	Name        string  `yaml:"name"`
	ShortName   string  `yaml:"shortName"`
	StartURL    string  `yaml:"startURL,omitempty"`
	Display     string  `yaml:"display,omitempty"`
	Description string  `yaml:"description,omitempty"`
	Dir         string  `yaml:"dir,omitempty"`
	Orientation string  `yaml:"orientation,omitempty"`
	Scope       string  `yaml:"scope,omitempty"`
	ThemeColor  string  `yaml:"themeColor,omitempty"`
	Background  string  `yaml:"backgroundColor,omitempty"`
	Icons       []*Icon `yaml:"icons"`
}

func (rss *RSS) sanitize(conf *Config, typ string) *helper.FieldError {
	if rss.Size <= 0 {
		return &helper.FieldError{Message: "必须大于 0", Field: typ + ".Size"}
	}
	if len(rss.URL) == 0 {
		return &helper.FieldError{Message: "不能为空", Field: typ + ".URL"}
	}

	switch typ {
	case "rss":
		rss.Type = contentTypeRSS
	case "atom":
		rss.Type = contentTypeAtom
	default:
		panic("无效的 typ 值")
	}

	if len(rss.Title) == 0 {
		rss.Title = conf.Title
	}

	return nil
}

// 检测 opensearch 取值是否正确
func (s *Opensearch) sanitize(conf *Config) *helper.FieldError {
	switch {
	case len(s.URL) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.url"}
	case len(s.ShortName) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.shortName"}
	case len(s.Description) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.description"}
	}

	if len(s.Type) == 0 {
		s.Type = contentTypeOpensearch
	}

	if s.Image == nil && conf.Icon != nil {
		s.Image = conf.Icon
	}

	return nil
}

// 检测 sitemap 取值是否正确
func (s *Sitemap) sanitize() *helper.FieldError {
	switch {
	case len(s.URL) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "sitemap.url"}
	case s.Priority > 1 || s.Priority < 0:
		return &helper.FieldError{Message: "介于[0,1]之间的浮点数", Field: "sitemap.priority"}
	case s.PostPriority > 1 || s.PostPriority < 0:
		return &helper.FieldError{Message: "介于[0,1]之间的浮点数", Field: "sitemap.postPriority"}
	case !isChangereq(s.Changefreq):
		return &helper.FieldError{Message: "取值不正确", Field: "sitemap.changefreq"}
	case !isChangereq(s.PostChangefreq):
		return &helper.FieldError{Message: "取值不正确", Field: "sitemap.postChangefreq"}
	}

	if len(s.Type) == 0 {
		s.Type = contentTypeXML
	}

	return nil
}

func (a *Archive) sanitize() *helper.FieldError {
	if len(a.Type) == 0 {
		a.Type = ArchiveTypeYear
	} else {
		if a.Type != ArchiveTypeMonth && a.Type != ArchiveTypeYear {
			return &helper.FieldError{Message: "取值不正确", Field: "archive.type"}
		}
	}

	if len(a.Order) == 0 {
		a.Order = ArchiveOrderDesc
	} else {
		if a.Order != ArchiveOrderAsc && a.Order != ArchiveOrderDesc {
			return &helper.FieldError{Message: "取值不正确", Field: "archive.order"}
		}
	}

	return nil
}

func (m *Manifest) sanitize(conf *Config) *helper.FieldError {
	if m.URL == "" {
		return &helper.FieldError{Message: "不能为空", Field: "pwa.url"}
	}

	if m.Type == "" {
		m.Type = contentManifest
	}

	if m.Lang == "" {
		m.Lang = conf.Language
	}

	if m.Name == "" {
		m.Name = conf.Title
	}

	if m.ShortName == "" {
		m.ShortName = conf.Subtitle
	}

	if m.StartURL == "" {
		m.StartURL = web.URL("")
	}

	if m.Display == "" {
		m.Display = "browser"
	} else {
		if !inStrings(m.Display, pwaDisplays) {
			return &helper.FieldError{Message: "取值不正确", Field: "pwa.display"}
		}
	}

	if len(m.Icons) == 0 { // nil 或是 len(m.Icons) == 0
		m.Icons = []*Icon{conf.Icon}
	}

	return nil
}

var changereqs = []string{
	"never",
	"yearly",
	"monthly",
	"weekly",
	"daily",
	"hourly",
	"always",
}

var pwaDisplays = []string{
	"fullscreen",
	"standalone",
	"minimal-ul",
	"browser",
}

func isChangereq(val string) bool {
	return inStrings(val, changereqs)
}

func inStrings(val string, vals []string) bool {
	for _, v := range vals {
		if v == val {
			return true
		}
	}
	return false
}
<commit_msg>添加对配置项中部分枚举类型的判断<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package loader

import (
	"github.com/issue9/web"

	"github.com/caixw/gitype/helper"
)

// 归档的类型
const (
	ArchiveTypeYear  = "year"
	ArchiveTypeMonth = "month"
)

// 归档的排序方式
const (
	ArchiveOrderDesc = "desc"
	ArchiveOrderAsc  = "asc"
)

// RSS RSS 和 Atom 相关的配置项
type RSS struct {
	Title string `yaml:"title"`
	URL   string `yaml:"url"`
	Type  string `yaml:"type,omitempty"`
	Size  int    `yaml:"size"` // 显示数量
}

// Opensearch opensearch 相关的配置
type Opensearch struct {
	URL   string `yaml:"url"`
	Type  string `yaml:"type,omitempty"`
	Title string `yaml:"title,omitempty"`

	ShortName   string `yaml:"shortName"`
	Description string `yaml:"description"`
	LongName    string `yaml:"longName,omitempty"`
	Image       *Icon  `yaml:"image,omitempty"`
}

// Sitemap sitemap 相关的配置
type Sitemap struct {
	URL  string `yaml:"url"`
	Type string `yaml:"type,omitempty"`

	XslURL     string  `yaml:"xslURL,omitempty"`    // 为 sitemap 指定一个 xsl 文件
	Priority   float64 `yaml:"priority"`            // 默认的优先级
	Changefreq string  `yaml:"changefreq"`          // 默认的更新频率
	EnableTag  bool    `yaml:"enableTag,omitempty"` // 是否将标签相关的页面写入 sitemap

	// 文章可以指定一个专门的值
	PostPriority   float64 `yaml:"postPriority"`
	PostChangefreq string  `yaml:"postChangefreq"`
}

// Archive 存档页的配置内容
type Archive struct {
	Order  string `yaml:"order"`            // 排序方式
	Type   string `yaml:"type,omitempty"`   // 存档的分类方式，可以按年或是按月
	Format string `yaml:"format,omitempty"` // 标题的格式化字符串
}

// Manifest 表示 PWA 中的相关配置
type Manifest struct {
	URL  string `yaml:"url"`
	Type string `yaml:"type,omitempty"`

	Lang        string  `yaml:"lang"`
	Name        string  `yaml:"name"`
	ShortName   string  `yaml:"shortName"`
	StartURL    string  `yaml:"startURL,omitempty"`
	Display     string  `yaml:"display,omitempty"`
	Description string  `yaml:"description,omitempty"`
	Dir         string  `yaml:"dir,omitempty"`
	Orientation string  `yaml:"orientation,omitempty"`
	Scope       string  `yaml:"scope,omitempty"`
	ThemeColor  string  `yaml:"themeColor,omitempty"`
	Background  string  `yaml:"backgroundColor,omitempty"`
	Icons       []*Icon `yaml:"icons"`
}

func (rss *RSS) sanitize(conf *Config, typ string) *helper.FieldError {
	if rss.Size <= 0 {
		return &helper.FieldError{Message: "必须大于 0", Field: typ + ".Size"}
	}
	if len(rss.URL) == 0 {
		return &helper.FieldError{Message: "不能为空", Field: typ + ".URL"}
	}

	switch typ {
	case "rss":
		rss.Type = contentTypeRSS
	case "atom":
		rss.Type = contentTypeAtom
	default:
		panic("无效的 typ 值")
	}

	if len(rss.Title) == 0 {
		rss.Title = conf.Title
	}

	return nil
}

// 检测 opensearch 取值是否正确
func (s *Opensearch) sanitize(conf *Config) *helper.FieldError {
	switch {
	case len(s.URL) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.url"}
	case len(s.ShortName) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.shortName"}
	case len(s.Description) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "opensearch.description"}
	}

	if len(s.Type) == 0 {
		s.Type = contentTypeOpensearch
	}

	if s.Image == nil && conf.Icon != nil {
		s.Image = conf.Icon
	}

	return nil
}

// 检测 sitemap 取值是否正确
func (s *Sitemap) sanitize() *helper.FieldError {
	switch {
	case len(s.URL) == 0:
		return &helper.FieldError{Message: "不能为空", Field: "sitemap.url"}
	case s.Priority > 1 || s.Priority < 0:
		return &helper.FieldError{Message: "介于[0,1]之间的浮点数", Field: "sitemap.priority"}
	case s.PostPriority > 1 || s.PostPriority < 0:
		return &helper.FieldError{Message: "介于[0,1]之间的浮点数", Field: "sitemap.postPriority"}
	case !isChangereq(s.Changefreq):
		return &helper.FieldError{Message: "取值不正确", Field: "sitemap.changefreq"}
	case !isChangereq(s.PostChangefreq):
		return &helper.FieldError{Message: "取值不正确", Field: "sitemap.postChangefreq"}
	}

	if len(s.Type) == 0 {
		s.Type = contentTypeXML
	}

	return nil
}

func (a *Archive) sanitize() *helper.FieldError {
	if len(a.Type) == 0 {
		a.Type = ArchiveTypeYear
	} else {
		if a.Type != ArchiveTypeMonth && a.Type != ArchiveTypeYear {
			return &helper.FieldError{Message: "取值不正确", Field: "archive.type"}
		}
	}

	if len(a.Order) == 0 {
		a.Order = ArchiveOrderDesc
	} else {
		if a.Order != ArchiveOrderAsc && a.Order != ArchiveOrderDesc {
			return &helper.FieldError{Message: "取值不正确", Field: "archive.order"}
		}
	}

	return nil
}

func (m *Manifest) sanitize(conf *Config) *helper.FieldError {
	if m.URL == "" {
		return &helper.FieldError{Message: "不能为空", Field: "pwa.url"}
	}

	if m.Type == "" {
		m.Type = contentManifest
	}

	if m.Lang == "" {
		m.Lang = conf.Language
	}

	if m.Name == "" {
		m.Name = conf.Title
	}

	if m.ShortName == "" {
		m.ShortName = conf.Subtitle
	}

	if m.StartURL == "" {
		m.StartURL = web.URL("")
	}

	if m.Display == "" {
		m.Display = "browser"
	} else if !inStrings(m.Display, pwaDisplays) {
		return &helper.FieldError{Message: "取值不正确", Field: "pwa.display"}
	}

	if m.Dir == "" {
		m.Dir = "auto"
	} else if !inStrings(m.Dir, pwaDirs) {
		return &helper.FieldError{Message: "取值不正确", Field: "pwa.dir"}
	}

	if m.Orientation != "" && !inStrings(m.Orientation, pwaOrientations) {
		return &helper.FieldError{Message: "取值不正确", Field: "pwa.orientation"}
	}

	if len(m.Icons) == 0 { // nil 或是 len(m.Icons) == 0
		m.Icons = []*Icon{conf.Icon}
	}

	return nil
}

var changereqs = []string{
	"never",
	"yearly",
	"monthly",
	"weekly",
	"daily",
	"hourly",
	"always",
}

var pwaDisplays = []string{
	"fullscreen",
	"standalone",
	"minimal-ul",
	"browser",
}

var pwaOrientations = []string{
	"any",
	"natural",
	"landscape",
	"landscape-primary",
	"landscape-secondary",
	"portrait",
	"portrait-primary",
	"portrait-secondary",
}

var pwaDirs = []string{
	"rtl",
	"ltr",
	"auto",
}

func isChangereq(val string) bool {
	return inStrings(val, changereqs)
}

func inStrings(val string, vals []string) bool {
	for _, v := range vals {
		if v == val {
			return true
		}
	}
	return false
}
<|endoftext|>