Compare commits
1 Commits
packages@6
...
v2.0.x
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0258617ee |
11
.babelrc
11
.babelrc
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": { "browsers": "last 3 versions" },
|
||||
"useBuiltIns": "entry"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
12
.bra.toml
12
.bra.toml
@@ -1,19 +1,17 @@
|
||||
[run]
|
||||
init_cmds = [
|
||||
["go", "run", "build.go", "-dev", "build-cli"],
|
||||
["go", "run", "build.go", "-dev", "build-server"],
|
||||
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
|
||||
["go", "build", "-o", "./bin/grafana-server"],
|
||||
["./bin/grafana-server"]
|
||||
]
|
||||
watch_all = true
|
||||
follow_symlinks = true
|
||||
watch_dirs = [
|
||||
"$WORKDIR/pkg",
|
||||
"$WORKDIR/public/views",
|
||||
"$WORKDIR/conf",
|
||||
]
|
||||
watch_exts = [".go", ".ini", ".toml", ".template.html"]
|
||||
watch_exts = [".go", ".ini"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
["go", "run", "build.go", "-dev", "build-server"],
|
||||
["./bin/grafana-server", "-packaging=dev", "cfg:app_mode=development"]
|
||||
["go", "build", "-o", "./bin/grafana-server"],
|
||||
["./bin/grafana-server"]
|
||||
]
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
>1%,
|
||||
Chrome > 20
|
||||
last 4 versions,
|
||||
Firefox ESR
|
||||
@@ -1,805 +0,0 @@
|
||||
aliases:
|
||||
# Workflow filters
|
||||
- &filter-only-release
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
- &filter-not-release-or-master
|
||||
tags:
|
||||
ignore: /^v[0-9]+(\.[0-9]+){2}(-.+|[^-.]*)$/
|
||||
branches:
|
||||
ignore: master
|
||||
- &filter-only-master
|
||||
branches:
|
||||
only: master
|
||||
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
mysql-integration-test:
|
||||
docker:
|
||||
- image: circleci/golang:1.12.6
|
||||
- image: circleci/mysql:5.6-ram
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_DATABASE: grafana_tests
|
||||
MYSQL_USER: grafana
|
||||
MYSQL_PASSWORD: password
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install -y mysql-client
|
||||
- run: dockerize -wait tcp://127.0.0.1:3306 -timeout 120s
|
||||
- run: cat devenv/docker/blocks/mysql_tests/setup.sql | mysql -h 127.0.0.1 -P 3306 -u root -prootpass
|
||||
- run:
|
||||
name: mysql integration tests
|
||||
command: './scripts/circle-test-mysql.sh'
|
||||
|
||||
postgres-integration-test:
|
||||
docker:
|
||||
- image: circleci/golang:1.12.6
|
||||
- image: circleci/postgres:9.3-ram
|
||||
environment:
|
||||
POSTGRES_USER: grafanatest
|
||||
POSTGRES_PASSWORD: grafanatest
|
||||
POSTGRES_DB: grafanatest
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: sudo apt update
|
||||
- run: sudo apt install -y postgresql-client
|
||||
- run: dockerize -wait tcp://127.0.0.1:5432 -timeout 120s
|
||||
- run: 'PGPASSWORD=grafanatest psql -p 5432 -h 127.0.0.1 -U grafanatest -d grafanatest -f devenv/docker/blocks/postgres_tests/setup.sql'
|
||||
- run:
|
||||
name: postgres integration tests
|
||||
command: './scripts/circle-test-postgres.sh'
|
||||
|
||||
cache-server-test:
|
||||
docker:
|
||||
- image: circleci/golang:1.12.6
|
||||
- image: circleci/redis:4-alpine
|
||||
- image: memcached
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run: dockerize -wait tcp://127.0.0.1:11211 -timeout 120s
|
||||
- run: dockerize -wait tcp://127.0.0.1:6379 -timeout 120s
|
||||
- run:
|
||||
name: cache server tests
|
||||
command: './scripts/circle-test-cache-servers.sh'
|
||||
|
||||
end-to-end-test:
|
||||
docker:
|
||||
- image: circleci/node:10-browsers
|
||||
- image: grafana/grafana-dev:master-$CIRCLE_SHA1
|
||||
steps:
|
||||
- run: dockerize -wait tcp://127.0.0.1:3000 -timeout 120s
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: yarn install
|
||||
command: 'yarn install --pure-lockfile --no-progress'
|
||||
no_output_timeout: 5m
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: run end-to-end tests
|
||||
command: 'env BASE_URL=http://127.0.0.1:3000 yarn e2e-tests'
|
||||
no_output_timeout: 5m
|
||||
- store_artifacts:
|
||||
path: public/e2e-test/screenShots/theTruth
|
||||
destination: expected-screenshots
|
||||
- store_artifacts:
|
||||
path: public/e2e-test/screenShots/theOutput
|
||||
destination: output-screenshots
|
||||
|
||||
codespell:
|
||||
docker:
|
||||
- image: circleci/python
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: install codespell
|
||||
command: 'sudo pip install codespell'
|
||||
- run:
|
||||
# Important: all words have to be in lowercase, and separated by "\n".
|
||||
name: exclude known exceptions
|
||||
command: 'echo -e "unknwon\nreferer\nerrorstring" > words_to_ignore.txt'
|
||||
- run:
|
||||
name: check documentation spelling errors
|
||||
command: 'codespell -I ./words_to_ignore.txt docs/'
|
||||
|
||||
lint-go:
|
||||
docker:
|
||||
- image: circleci/golang:1.12.6
|
||||
environment:
|
||||
# we need CGO because of go-sqlite3
|
||||
CGO_ENABLED: 1
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Lint Go
|
||||
command: 'make lint-go'
|
||||
|
||||
test-frontend:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- checkout
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: yarn install
|
||||
command: 'yarn install --pure-lockfile --no-progress'
|
||||
no_output_timeout: 15m
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- run:
|
||||
name: frontend tests
|
||||
command: './scripts/circle-test-frontend.sh'
|
||||
|
||||
test-backend:
|
||||
docker:
|
||||
- image: circleci/golang:1.12.6
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: build backend and run go tests
|
||||
command: './scripts/circle-test-backend.sh'
|
||||
|
||||
build-all:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- restore_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
- run:
|
||||
name: download phantomjs binaries
|
||||
command: './scripts/build/download-phantomjs.sh'
|
||||
- save_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
paths:
|
||||
- /tmp/phantomjs
|
||||
- run:
|
||||
name: build and package grafana
|
||||
command: './scripts/build/build-all.sh'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
- run:
|
||||
name: verify signed packages
|
||||
command: |
|
||||
mkdir -p ~/.rpmdb/pubkeys
|
||||
curl -s https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana > ~/.rpmdb/pubkeys/grafana.key
|
||||
./scripts/build/verify_signed_packages.sh dist/*.rpm
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Test and build Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test . && go build -o release_publisher .'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- dist/*
|
||||
- scripts/*.sh
|
||||
- scripts/build/release_publisher/release_publisher
|
||||
- scripts/build/publish.sh
|
||||
|
||||
build:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: build and package grafana
|
||||
command: './scripts/build/build.sh'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Test Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test .'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- dist/*
|
||||
|
||||
build-fast-backend:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: build grafana backend
|
||||
command: './scripts/build/build.sh --fast --backend-only'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- bin/*
|
||||
|
||||
build-fast-frontend:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- restore_cache:
|
||||
key: frontend-dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: build grafana frontend
|
||||
command: './scripts/build/build.sh --fast --frontend-only'
|
||||
- save_cache:
|
||||
key: frontend-dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- node_modules
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- public/build/*
|
||||
- tools/phantomjs/*
|
||||
|
||||
build-fast-package:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- restore_cache:
|
||||
key: frontend-dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: package grafana
|
||||
command: './scripts/build/build.sh --fast --package-only'
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Test Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test .'
|
||||
- persist_to_workspace:
|
||||
root: /go/src/github.com/grafana/grafana
|
||||
paths:
|
||||
- dist/*
|
||||
|
||||
build-fast-save:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- restore_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
- run:
|
||||
name: debug cache
|
||||
command: 'ls -al /go/src/github.com/grafana/grafana/node_modules'
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: build grafana backend
|
||||
command: './scripts/build/build.sh --fast --backend-only'
|
||||
- run:
|
||||
name: build grafana frontend
|
||||
command: './scripts/build/build.sh --fast --frontend-only'
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- /go/src/github.com/grafana/grafana/node_modules
|
||||
- run:
|
||||
name: package grafana
|
||||
command: './scripts/build/build.sh --fast --package-only'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: Test Grafana.com release publisher
|
||||
command: 'cd scripts/build/release_publisher && go test .'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- dist/*
|
||||
|
||||
grafana-docker-master:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: docker info
|
||||
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||
- run: cd packaging/docker && ./build-deploy.sh "master-${CIRCLE_SHA1}"
|
||||
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||
- run: cd packaging/docker && ./build-enterprise.sh "master"
|
||||
|
||||
|
||||
grafana-docker-pr:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: docker info
|
||||
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||
- run: cd packaging/docker && ./build.sh --fast "${CIRCLE_SHA1}"
|
||||
|
||||
grafana-docker-release:
|
||||
machine:
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: docker info
|
||||
- run: docker run --privileged linuxkit/binfmt:v0.6
|
||||
- run: cp dist/grafana-latest.linux-*.tar.gz packaging/docker
|
||||
- run: cd packaging/docker && ./build-deploy.sh "${CIRCLE_TAG}"
|
||||
- run: rm packaging/docker/grafana-latest.linux-*.tar.gz
|
||||
- run: cp enterprise-dist/grafana-enterprise-*.linux-amd64.tar.gz packaging/docker/grafana-latest.linux-x64.tar.gz
|
||||
- run: cd packaging/docker && ./build-enterprise.sh "${CIRCLE_TAG}"
|
||||
|
||||
build-enterprise:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: checkout enterprise
|
||||
command: './scripts/build/prepare-enterprise.sh'
|
||||
- run:
|
||||
name: test enterprise
|
||||
command: 'go test ./pkg/extensions/...'
|
||||
- run:
|
||||
name: build and package enterprise
|
||||
command: './scripts/build/build.sh -enterprise'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: move enterprise packages into their own folder
|
||||
command: 'mv dist enterprise-dist'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- enterprise-dist/*
|
||||
|
||||
build-all-enterprise:
|
||||
docker:
|
||||
- image: grafana/build-container:1.2.7
|
||||
working_directory: /go/src/github.com/grafana/grafana
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: prepare build tools
|
||||
command: '/tmp/bootstrap.sh'
|
||||
- run:
|
||||
name: checkout enterprise
|
||||
command: './scripts/build/prepare-enterprise.sh'
|
||||
- restore_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
- run:
|
||||
name: download phantomjs binaries
|
||||
command: './scripts/build/download-phantomjs.sh'
|
||||
- save_cache:
|
||||
key: phantomjs-binaries-{{ checksum "scripts/build/download-phantomjs.sh" }}
|
||||
paths:
|
||||
- /tmp/phantomjs
|
||||
- run:
|
||||
name: test enterprise
|
||||
command: 'go test ./pkg/extensions/...'
|
||||
- run:
|
||||
name: build and package grafana
|
||||
command: './scripts/build/build-all.sh -enterprise'
|
||||
- run:
|
||||
name: sign packages
|
||||
command: './scripts/build/sign_packages.sh'
|
||||
- run:
|
||||
name: verify signed packages
|
||||
command: |
|
||||
mkdir -p ~/.rpmdb/pubkeys
|
||||
curl -s https://grafanarel.s3.amazonaws.com/RPM-GPG-KEY-grafana > ~/.rpmdb/pubkeys/grafana.key
|
||||
./scripts/build/verify_signed_packages.sh dist/*.rpm
|
||||
- run:
|
||||
name: sha-sum packages
|
||||
command: 'go run build.go sha-dist'
|
||||
- run:
|
||||
name: move enterprise packages into their own folder
|
||||
command: 'mv dist enterprise-dist'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- enterprise-dist/*
|
||||
|
||||
deploy-enterprise-master:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: gcp credentials
|
||||
command: 'echo ${GCP_GRAFANA_UPLOAD_KEY} > /tmp/gcpkey.json'
|
||||
- run:
|
||||
name: sign in to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file=/tmp/gcpkey.json'
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: 'aws s3 sync ./enterprise-dist s3://$ENTERPRISE_BUCKET_NAME/master'
|
||||
- run:
|
||||
name: deploy to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/master'
|
||||
- run:
|
||||
name: Deploy to grafana.com
|
||||
command: |
|
||||
cd enterprise-dist
|
||||
../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -enterprise -version "v$(cat grafana.version)" --nightly
|
||||
|
||||
|
||||
deploy-enterprise-release:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: gcp credentials
|
||||
command: 'echo ${GCP_GRAFANA_UPLOAD_KEY} > /tmp/gcpkey.json'
|
||||
- run:
|
||||
name: sign in to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file=/tmp/gcpkey.json'
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: 'aws s3 sync ./enterprise-dist s3://$ENTERPRISE_BUCKET_NAME/release'
|
||||
- run:
|
||||
name: deploy to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gsutil cp ./enterprise-dist/* gs://$GCP_BUCKET_NAME/enterprise/release'
|
||||
- run:
|
||||
name: Deploy to Grafana.com
|
||||
command: './scripts/build/publish.sh --enterprise'
|
||||
- run:
|
||||
name: Load GPG private key
|
||||
command: './scripts/build/load-signing-key.sh'
|
||||
- run:
|
||||
name: Update Debian repository
|
||||
command: './scripts/build/update_repo/update-deb.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "enterprise-dist"'
|
||||
- run:
|
||||
name: Update RPM repository
|
||||
command: './scripts/build/update_repo/update-rpm.sh "enterprise" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "enterprise-dist"'
|
||||
|
||||
|
||||
deploy-master:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: |
|
||||
# Also
|
||||
cp dist/grafana-latest.linux-x64.tar.gz dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
|
||||
aws s3 sync ./dist s3://$BUCKET_NAME/master
|
||||
- run:
|
||||
name: Trigger Windows build
|
||||
command: './scripts/trigger_windows_build.sh ${APPVEYOR_TOKEN} ${CIRCLE_SHA1} master'
|
||||
- run:
|
||||
name: gcp credentials
|
||||
command: 'echo ${GCP_GRAFANA_UPLOAD_KEY} > /tmp/gcpkey.json'
|
||||
- run:
|
||||
name: sign in to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file=/tmp/gcpkey.json'
|
||||
- run:
|
||||
name: deploy to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gsutil cp ./dist/* gs://$GCP_BUCKET_NAME/oss/master'
|
||||
- run:
|
||||
name: Publish to Grafana.com
|
||||
command: |
|
||||
rm dist/grafana-master-$(echo "${CIRCLE_SHA1}" | cut -b1-7).linux-x64.tar.gz
|
||||
rm dist/*latest*
|
||||
cd dist && ../scripts/build/release_publisher/release_publisher -apikey ${GRAFANA_COM_API_KEY} -version "v$(cat grafana.version)" --nightly
|
||||
|
||||
deploy-release:
|
||||
docker:
|
||||
- image: grafana/grafana-ci-deploy:1.2.2
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: deploy to s3
|
||||
command: 'aws s3 sync ./dist s3://$BUCKET_NAME/release'
|
||||
- run:
|
||||
name: gcp credentials
|
||||
command: 'echo ${GCP_GRAFANA_UPLOAD_KEY} > /tmp/gcpkey.json'
|
||||
- run:
|
||||
name: sign in to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gcloud auth activate-service-account --key-file=/tmp/gcpkey.json'
|
||||
- run:
|
||||
name: deploy to gcp
|
||||
command: '/opt/google-cloud-sdk/bin/gsutil cp ./dist/* gs://$GCP_BUCKET_NAME/oss/release'
|
||||
- run:
|
||||
name: Deploy to Grafana.com
|
||||
command: './scripts/build/publish.sh'
|
||||
- run:
|
||||
name: Load GPG private key
|
||||
command: './scripts/build/load-signing-key.sh'
|
||||
- run:
|
||||
name: Update Debian repository
|
||||
command: './scripts/build/update_repo/update-deb.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "dist"'
|
||||
- run:
|
||||
name: Update RPM repository
|
||||
command: './scripts/build/update_repo/update-rpm.sh "oss" "$GPG_KEY_PASSWORD" "$CIRCLE_TAG" "dist"'
|
||||
|
||||
build-oss-msi:
|
||||
docker:
|
||||
- image: grafana/wix-toolset-ci:v3
|
||||
steps:
|
||||
- checkout
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run:
|
||||
name: Build OSS MSI
|
||||
command: './scripts/build/ci-msi-build/ci-msi-build-oss.sh'
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- dist/grafana-*.msi
|
||||
- dist/grafana-*.msi.sha256
|
||||
|
||||
store-build-artifacts:
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- store_artifacts:
|
||||
path: ./dist
|
||||
|
||||
trigger-docs-update:
|
||||
docker:
|
||||
- image: circleci/python:3.6.8
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Trigger Docs update
|
||||
command: |
|
||||
if git diff --name-only HEAD^ | grep -q "^docs"; then
|
||||
echo "Build URL:"
|
||||
curl -s -u "$DOCS_CIRCLE_TOKEN:" \
|
||||
-d build_parameters[CIRCLE_JOB]=pull-submodule-changes \
|
||||
https://circleci.com/api/v1.1/project/github/grafana/docs.grafana.com/tree/staging \
|
||||
| jq .build_url
|
||||
else
|
||||
echo "-- no changes to docs files --"
|
||||
fi
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-master:
|
||||
jobs:
|
||||
- build-all:
|
||||
filters: *filter-only-master
|
||||
- build-all-enterprise:
|
||||
filters: *filter-only-master
|
||||
- codespell:
|
||||
filters: *filter-only-master
|
||||
- lint-go:
|
||||
filters: *filter-only-master
|
||||
- test-frontend:
|
||||
filters: *filter-only-master
|
||||
- test-backend:
|
||||
filters: *filter-only-master
|
||||
- mysql-integration-test:
|
||||
filters: *filter-only-master
|
||||
- postgres-integration-test:
|
||||
filters: *filter-only-master
|
||||
- deploy-master:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- build-oss-msi
|
||||
filters: *filter-only-master
|
||||
- grafana-docker-master:
|
||||
requires:
|
||||
- build-all
|
||||
- build-all-enterprise
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-master
|
||||
- deploy-enterprise-master:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- build-all-enterprise
|
||||
filters: *filter-only-master
|
||||
- build-oss-msi:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-master
|
||||
- end-to-end-test:
|
||||
requires:
|
||||
- grafana-docker-master
|
||||
filters: *filter-only-master
|
||||
- trigger-docs-update:
|
||||
requires:
|
||||
- end-to-end-test
|
||||
filters: *filter-only-master
|
||||
release:
|
||||
jobs:
|
||||
- build-all:
|
||||
filters: *filter-only-release
|
||||
- build-all-enterprise:
|
||||
filters: *filter-only-release
|
||||
- codespell:
|
||||
filters: *filter-only-release
|
||||
- lint-go:
|
||||
filters: *filter-only-release
|
||||
- test-frontend:
|
||||
filters: *filter-only-release
|
||||
- test-backend:
|
||||
filters: *filter-only-release
|
||||
- mysql-integration-test:
|
||||
filters: *filter-only-release
|
||||
- postgres-integration-test:
|
||||
filters: *filter-only-release
|
||||
- deploy-release:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- build-oss-msi
|
||||
filters: *filter-only-release
|
||||
- deploy-enterprise-release:
|
||||
requires:
|
||||
- build-all
|
||||
- build-all-enterprise
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-release
|
||||
- grafana-docker-release:
|
||||
requires:
|
||||
- build-all
|
||||
- build-all-enterprise
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-release
|
||||
- build-oss-msi:
|
||||
requires:
|
||||
- build-all
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
filters: *filter-only-release
|
||||
|
||||
build-branches-and-prs:
|
||||
jobs:
|
||||
- build-fast-backend:
|
||||
filters: *filter-not-release-or-master
|
||||
- build-fast-frontend:
|
||||
filters: *filter-not-release-or-master
|
||||
- build-fast-package:
|
||||
filters: *filter-not-release-or-master
|
||||
requires:
|
||||
- build-fast-backend
|
||||
- build-fast-frontend
|
||||
- codespell:
|
||||
filters: *filter-not-release-or-master
|
||||
- lint-go:
|
||||
filters: *filter-not-release-or-master
|
||||
- test-frontend:
|
||||
filters: *filter-not-release-or-master
|
||||
- test-backend:
|
||||
filters: *filter-not-release-or-master
|
||||
- mysql-integration-test:
|
||||
filters: *filter-not-release-or-master
|
||||
- postgres-integration-test:
|
||||
filters: *filter-not-release-or-master
|
||||
- cache-server-test:
|
||||
filters: *filter-not-release-or-master
|
||||
- grafana-docker-pr:
|
||||
requires:
|
||||
- build-fast-package
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- cache-server-test
|
||||
filters: *filter-not-release-or-master
|
||||
- store-build-artifacts:
|
||||
requires:
|
||||
- build-fast-package
|
||||
- test-backend
|
||||
- test-frontend
|
||||
- codespell
|
||||
- lint-go
|
||||
- mysql-integration-test
|
||||
- postgres-integration-test
|
||||
- cache-server-test
|
||||
filters: *filter-not-release-or-master
|
||||
@@ -1,18 +0,0 @@
|
||||
.awcache
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
.github
|
||||
.vscode
|
||||
bin
|
||||
data*
|
||||
dist
|
||||
docker
|
||||
Dockerfile
|
||||
docs
|
||||
dump.rdb
|
||||
node_modules
|
||||
/local
|
||||
/tmp
|
||||
*.yml
|
||||
*.md
|
||||
@@ -1,24 +0,0 @@
|
||||
# http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
max_line_length = 120
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
27
.github/ISSUE_TEMPLATE/1-bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/1-bug_report.md
vendored
@@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report a bug you found when using Grafana
|
||||
labels: 'type: bug'
|
||||
---
|
||||
|
||||
<!--
|
||||
Please use this template while reporting a bug and provide as much info as possible.
|
||||
Questions should be posted to https://community.grafana.com
|
||||
Use query inspector to troubleshoot issues: https://community.grafana.com/t/using-grafanas-query-inspector-to-troubleshoot-issues/2630
|
||||
-->
|
||||
|
||||
**What happened**:
|
||||
|
||||
**What you expected to happen**:
|
||||
|
||||
**How to reproduce it (as minimally and precisely as possible)**:
|
||||
|
||||
**Anything else we need to know?**:
|
||||
|
||||
**Environment**:
|
||||
- Grafana version:
|
||||
- Data source type & version:
|
||||
- OS Grafana is installed on:
|
||||
- User OS & Browser:
|
||||
- Grafana plugins:
|
||||
- Others:
|
||||
11
.github/ISSUE_TEMPLATE/2-feature_request.md
vendored
11
.github/ISSUE_TEMPLATE/2-feature_request.md
vendored
@@ -1,11 +0,0 @@
|
||||
---
|
||||
name: Enhancement request
|
||||
about: Suggest an enhancement or new feature for the Grafana project
|
||||
labels: 'type: feature request'
|
||||
---
|
||||
|
||||
<!-- Please only use this template for submitting feature requests -->
|
||||
|
||||
**What would you like to be added**:
|
||||
|
||||
**Why is this needed**:
|
||||
26
.github/ISSUE_TEMPLATE/3-accessibility.md
vendored
26
.github/ISSUE_TEMPLATE/3-accessibility.md
vendored
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: Accessibility issue
|
||||
about: Help make Grafana be better at keyboard navigation, screen-readable and accessible to all.
|
||||
labels: 'type: accessibility'
|
||||
---
|
||||
|
||||
<!--
|
||||
Please only use this template for submitting accessibility issues.
|
||||
|
||||
This is a new feature area for Grafana that we want to improve. We have long way to go
|
||||
to really improve accessibility and would like your help to know where to start.
|
||||
-->
|
||||
|
||||
**Steps to reproduce**:
|
||||
|
||||
**Actual Result**:
|
||||
|
||||
**Expected Result**
|
||||
|
||||
**Relevant WCAG Criteria:** [#.#.# WCAG Criterion](link to https://www.w3.org/WAI/WCAG21/quickref/?versions=2.0)
|
||||
|
||||
**Environment**:
|
||||
- Grafana version:
|
||||
- Data source type & version:
|
||||
- User OS & Browser:
|
||||
- Others:
|
||||
14
.github/ISSUE_TEMPLATE/4-question.md
vendored
14
.github/ISSUE_TEMPLATE/4-question.md
vendored
@@ -1,14 +0,0 @@
|
||||
---
|
||||
name: Support request
|
||||
about: 'Question or support request relating to using Grafana'
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
STOP -- PLEASE READ!
|
||||
|
||||
GitHub is not the right place for questions and support requests.
|
||||
|
||||
Please ask questions on our community site: [https://community.grafana.com/](https://community.grafana.com/)
|
||||
|
||||
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,21 +0,0 @@
|
||||
<!-- Thanks for sending a pull request! Here are some tips for you:
|
||||
|
||||
1. If this is your first time, please read our [`CONTRIBUTING.md`](https://github.com/grafana/grafana/blob/master/CONTRIBUTING.md) guide.
|
||||
2. Ensure you have added or ran the appropriate tests for your PR.
|
||||
3. If it's a new feature or config option it will need a docs update. Docs are under the docs folder in repo root.
|
||||
4. If the PR is unfinished, mark it as a draft PR.
|
||||
5. Rebase your PR if it gets out of sync with master
|
||||
6. Name your PR as `<FeatureArea>: Describe your change`. If it's a fix or feature relevant for changelog describe the user impact in the title. The PR title is used in changelog for issues marked with `add to changelog` label.
|
||||
-->
|
||||
|
||||
**What this PR does / why we need it**:
|
||||
|
||||
**Which issue(s) this PR fixes**:
|
||||
<!--
|
||||
*Automatically closes linked issue when PR is merged.
|
||||
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
|
||||
-->
|
||||
Fixes #
|
||||
|
||||
**Special notes for your reviewer**:
|
||||
|
||||
65
.gitignore
vendored
65
.gitignore
vendored
@@ -1,27 +1,16 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
coverage/
|
||||
.aws-config.json
|
||||
awsconfig
|
||||
/.awcache
|
||||
/dist
|
||||
/public/build
|
||||
/public/views/index.html
|
||||
/public/views/error.html
|
||||
/emails/dist
|
||||
/public_gen
|
||||
/public/vendor/npm
|
||||
/tmp
|
||||
tools/phantomjs/phantomjs
|
||||
tools/phantomjs/phantomjs.exe
|
||||
profile.out
|
||||
coverage.txt
|
||||
|
||||
docs/AWS_S3_BUCKET
|
||||
docs/GIT_BRANCH
|
||||
docs/VERSION
|
||||
docs/GITCOMMIT
|
||||
docs/changed-files
|
||||
docs/changed-files
|
||||
|
||||
# locally required config files
|
||||
public/css/*.min.css
|
||||
@@ -31,60 +20,10 @@ public/css/*.min.css
|
||||
*.swp
|
||||
.idea/
|
||||
*.iml
|
||||
*.tmp
|
||||
.DS_Store
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
/data/*
|
||||
/bin/*
|
||||
|
||||
conf/custom.ini
|
||||
fig.yml
|
||||
devenv/docker-compose.yml
|
||||
devenv/docker-compose.yaml
|
||||
/conf/provisioning/**/custom.yaml
|
||||
/conf/provisioning/**/dev.yaml
|
||||
/conf/ldap_dev.toml
|
||||
/conf/ldap_freeipa.toml
|
||||
profile.cov
|
||||
/grafana
|
||||
/local
|
||||
.notouch
|
||||
/Makefile.local
|
||||
/pkg/cmd/grafana-cli/grafana-cli
|
||||
/pkg/cmd/grafana-server/grafana-server
|
||||
/pkg/cmd/grafana-server/debug
|
||||
/pkg/extensions
|
||||
/public/app/extensions
|
||||
debug.test
|
||||
/examples/*/dist
|
||||
/packaging/**/*.rpm
|
||||
/packaging/**/*.deb
|
||||
/packaging/**/*.tar.gz
|
||||
|
||||
# Ignore OSX indexing
|
||||
.DS_Store
|
||||
|
||||
/vendor/**/*.py
|
||||
/vendor/**/*.xml
|
||||
/vendor/**/*.yml
|
||||
/vendor/**/*_test.go
|
||||
/vendor/**/.editorconfig
|
||||
*.orig
|
||||
|
||||
/devenv/bulk-dashboards/*.json
|
||||
/devenv/bulk_alerting_dashboards/*.json
|
||||
|
||||
/scripts/build/release_publisher/release_publisher
|
||||
*.patch
|
||||
|
||||
# Ignoring frontend packages specifics
|
||||
/packages/**/dist
|
||||
/packages/**/compiled
|
||||
/packages/**/.rpt2_cache
|
||||
|
||||
theOutput/
|
||||
|
||||
# Ignore go local build dependencies
|
||||
/scripts/go/bin/**
|
||||
|
||||
7
.hooks/pre-commit
Executable file
7
.hooks/pre-commit
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
test -z "$(gofmt -s -l . | grep -v Godeps/_workspace/src/ | tee /dev/stderr)"
|
||||
if [ $? -gt 0 ]; then
|
||||
echo "Some files aren't formatted, please run 'go fmt ./pkg/...' to format your source code before committing"
|
||||
exit 1
|
||||
fi
|
||||
13
.jscs.json
Normal file
13
.jscs.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"disallowImplicitTypeConversion": ["string"],
|
||||
"disallowKeywords": ["with"],
|
||||
"disallowMultipleLineBreaks": true,
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"requireSpacesInFunctionExpression": {
|
||||
"beforeOpeningCurlyBrace": true
|
||||
},
|
||||
"disallowSpacesInsideArrayBrackets": true,
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
"validateIndentation": 2
|
||||
}
|
||||
21
.jsfmtrc
Normal file
21
.jsfmtrc
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"preset" : "default",
|
||||
|
||||
"lineBreak" : {
|
||||
"before" : {
|
||||
"VariableDeclarationWithoutInit" : 0,
|
||||
},
|
||||
|
||||
"after": {
|
||||
"AssignmentOperator": -1,
|
||||
"ArgumentListArrayExpression": ">=1"
|
||||
}
|
||||
},
|
||||
|
||||
"whiteSpace" : {
|
||||
"before" : {
|
||||
},
|
||||
"after" : {
|
||||
}
|
||||
}
|
||||
}
|
||||
35
.jshintrc
Normal file
35
.jshintrc
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"browser": true,
|
||||
|
||||
"bitwise":false,
|
||||
"curly": true,
|
||||
"eqnull": true,
|
||||
"globalstrict": true,
|
||||
"devel": true,
|
||||
"eqeqeq": true,
|
||||
"forin": false,
|
||||
"immed": true,
|
||||
"supernew": true,
|
||||
"expr": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"noempty": true,
|
||||
"undef": true,
|
||||
"boss": true,
|
||||
"trailing": true,
|
||||
"laxbreak": true,
|
||||
"laxcomma": true,
|
||||
"sub": true,
|
||||
"unused": true,
|
||||
"maxdepth": 5,
|
||||
"maxlen": 140,
|
||||
|
||||
"globals": {
|
||||
"define": true,
|
||||
"require": true,
|
||||
"Chromath": false,
|
||||
"setImmediate": true
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
.git
|
||||
.github
|
||||
dist/
|
||||
pkg/
|
||||
node_modules
|
||||
public/vendor/
|
||||
vendor/
|
||||
data/
|
||||
|
||||
2003
CHANGELOG.md
2003
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -1,46 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at contact@grafana.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
||||
@@ -1,62 +1,14 @@
|
||||
# Contributing
|
||||
If you have any idea for an improvement or found a bug do not hesitate to open an issue.
|
||||
And if you have time clone this repo and submit a pull request and help me make Grafana the
|
||||
kickass metrics & devops dashboard we all dream about!
|
||||
|
||||
Grafana uses GitHub to manage contributions.
|
||||
Contributions take the form of pull requests that will be reviewed by the core team.
|
||||
Prerequisites:
|
||||
- Nodejs (for jshint & grunt & development server)
|
||||
|
||||
- If you are a new contributor see: [Steps to Contribute](#steps-to-contribute)
|
||||
Clone repository:
|
||||
|
||||
- If you have a trivial fix or improvement, go ahead and create a pull request.
|
||||
npm install
|
||||
grunt server (starts development web server in src folder)
|
||||
grunt (runs jshint and less -> css compilation)
|
||||
|
||||
- If you plan to do something more involved, discuss your idea on the respective [issue](https://github.com/grafana/grafana/issues) or create a [new issue](https://github.com/grafana/grafana/issues/new) if it does not exist. This will avoid unnecessary work and surely give you and us a good deal of inspiration.
|
||||
|
||||
- Sign our [CLA](http://docs.grafana.org/contribute/cla/).
|
||||
|
||||
- Make sure to follow the code style guides
|
||||
- [Backend](https://github.com/grafana/grafana/tree/master/pkg)
|
||||
- [Frontend](https://github.com/grafana/grafana/tree/master/style_guides)
|
||||
|
||||
## Steps to Contribute
|
||||
|
||||
Should you wish to work on a GitHub issue, check first if it is not already assigned to someone. If it is free, you claim it by commenting on the issue that you want to work on it. This is to prevent duplicated efforts from contributors on the same issue.
|
||||
|
||||
Please check the [`beginner friendly`](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22beginner+friendly%22) and [`help wanted`](https://github.com/grafana/grafana/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) labels to find issues that are good for getting started. If you have questions about one of the issues, with or without the tag, please comment on them and one of the core team or the original poster will clarify it.
|
||||
|
||||
To setup a local development environment we recommend reading [Building Grafana from source](http://docs.grafana.org/project/building_from_source/)
|
||||
|
||||
## Pull Request Checklist
|
||||
|
||||
- Branch from the master branch and, if needed, rebase to the current master branch before submitting your pull request. If it doesn't merge cleanly with master you may be asked to rebase your changes.
|
||||
|
||||
- If your patch is not getting reviewed or you need a specific person to review it, you can @-reply a reviewer asking for a review in the pull request or a comment.
|
||||
|
||||
- Add tests relevant to the fixed bug or new feature.
|
||||
|
||||
- Follow [PR and commit messages guidelines](#PR-and-commit-messages-guidelines)
|
||||
|
||||
### Pull Requests titles and message
|
||||
|
||||
Pull request titles should follow this format: `Area: Name of the change`.
|
||||
Titles are used to generate the changelog so they should be as descriptive as possible in one line.
|
||||
|
||||
Good Examples
|
||||
|
||||
- `Explore: Adds Live option for supported datasources`
|
||||
- `GraphPanel: Don't sort series when legend table & sort column is not visible`
|
||||
- `Build: Support publishing MSI to grafana.com`
|
||||
|
||||
The message in the Pull requests should contain a reference so the issue if there is one. Ex `Closes #<issue number>`, `Fixes #<issue number>`, or `Ref #<issue number>` if the change is related to an issue but does not close it. Make sure to explain what problem the pull request is solving and why its implemented this way. As a new contributor its often better to overcommunicate to avoid back and forth communication, as it consumes time and energy.
|
||||
|
||||
### GIT commit formating.
|
||||
|
||||
Grafana Squash Pull requests when merging them into master. This means the maintainer will be responsible for the title in the git commit message.
|
||||
The commit message of the commits in the Pull Request can still be part of the git commit body. So it's always encouraged to write informative commit messages.
|
||||
|
||||
The Git commit title should be short, descriptive and include the Pull Request ID.
|
||||
|
||||
Good Examples
|
||||
|
||||
- `Explore: Live supprt in datasources (#12345)`
|
||||
- `GraphPanel: Fix legend sorting issues (#12345)`
|
||||
- `Build: Support publishing MSI to grafana.com (#12345)`
|
||||
|
||||
Its also good practice to include a reference to the issue in the git commit body when possible.
|
||||
Please remember to run grunt before doing pull request to verify that your code passes all the jshint validations.
|
||||
|
||||
84
Dockerfile
84
Dockerfile
@@ -1,84 +0,0 @@
|
||||
# Golang build container
|
||||
FROM golang:1.12.4
|
||||
|
||||
WORKDIR $GOPATH/src/github.com/grafana/grafana
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY vendor vendor
|
||||
|
||||
RUN go mod verify
|
||||
|
||||
COPY pkg pkg
|
||||
COPY build.go build.go
|
||||
COPY package.json package.json
|
||||
|
||||
RUN go run build.go build
|
||||
|
||||
# Node build container
|
||||
FROM node:10.14.2
|
||||
|
||||
WORKDIR /usr/src/app/
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
COPY packages packages
|
||||
|
||||
RUN yarn install --pure-lockfile --no-progress
|
||||
|
||||
COPY Gruntfile.js tsconfig.json tslint.json ./
|
||||
COPY public public
|
||||
COPY scripts scripts
|
||||
COPY emails emails
|
||||
|
||||
ENV NODE_ENV production
|
||||
RUN ./node_modules/.bin/grunt build
|
||||
|
||||
# Final container
|
||||
FROM ubuntu:latest
|
||||
|
||||
LABEL maintainer="Grafana team <hello@grafana.com>"
|
||||
|
||||
ARG GF_UID="472"
|
||||
ARG GF_GID="472"
|
||||
|
||||
ENV PATH=/usr/share/grafana/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
|
||||
GF_PATHS_CONFIG="/etc/grafana/grafana.ini" \
|
||||
GF_PATHS_DATA="/var/lib/grafana" \
|
||||
GF_PATHS_HOME="/usr/share/grafana" \
|
||||
GF_PATHS_LOGS="/var/log/grafana" \
|
||||
GF_PATHS_PLUGINS="/var/lib/grafana/plugins" \
|
||||
GF_PATHS_PROVISIONING="/etc/grafana/provisioning"
|
||||
|
||||
WORKDIR $GF_PATHS_HOME
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y && \
|
||||
apt-get install -qq -y libfontconfig1 ca-certificates && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY conf ./conf
|
||||
|
||||
RUN mkdir -p "$GF_PATHS_HOME/.aws" && \
|
||||
groupadd -r -g $GF_GID grafana && \
|
||||
useradd -r -u $GF_UID -g grafana grafana && \
|
||||
mkdir -p "$GF_PATHS_PROVISIONING/datasources" \
|
||||
"$GF_PATHS_PROVISIONING/dashboards" \
|
||||
"$GF_PATHS_PROVISIONING/notifiers" \
|
||||
"$GF_PATHS_LOGS" \
|
||||
"$GF_PATHS_PLUGINS" \
|
||||
"$GF_PATHS_DATA" && \
|
||||
cp "$GF_PATHS_HOME/conf/sample.ini" "$GF_PATHS_CONFIG" && \
|
||||
cp "$GF_PATHS_HOME/conf/ldap.toml" /etc/grafana/ldap.toml && \
|
||||
chown -R grafana:grafana "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING" && \
|
||||
chmod 777 -R "$GF_PATHS_DATA" "$GF_PATHS_HOME/.aws" "$GF_PATHS_LOGS" "$GF_PATHS_PLUGINS" "$GF_PATHS_PROVISIONING"
|
||||
|
||||
COPY --from=0 /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-server /go/src/github.com/grafana/grafana/bin/linux-amd64/grafana-cli ./bin/
|
||||
COPY --from=1 /usr/src/app/public ./public
|
||||
COPY --from=1 /usr/src/app/tools ./tools
|
||||
COPY tools/phantomjs/render.js ./tools/phantomjs/render.js
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
COPY ./packaging/docker/run.sh /run.sh
|
||||
|
||||
USER grafana
|
||||
ENTRYPOINT [ "/run.sh" ]
|
||||
96
Godeps/Godeps.json
generated
Normal file
96
Godeps/Godeps.json
generated
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"ImportPath": "github.com/grafana/grafana",
|
||||
"GoVersion": "go1.3",
|
||||
"Packages": [
|
||||
"./pkg/..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Unknwon/com",
|
||||
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Unknwon/macaron",
|
||||
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dalu/slug",
|
||||
"Rev": "6dbd13912e9be466e2c1de349a2c7d1466c97e07"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/dalu/unidecode",
|
||||
"Rev": "339814d47f3e32a6f7036a0a4c56ed9b373dd755"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-sql-driver/mysql",
|
||||
"Comment": "v1.2-26-g9543750",
|
||||
"Rev": "9543750295406ef070f7de8ae9c43ccddd44e15e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-xorm/core",
|
||||
"Rev": "be6e7ac47dc57bd0ada25322fa526944f66ccaa6"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/go-xorm/xorm",
|
||||
"Comment": "v0.4.2-58-ge2889e5",
|
||||
"Rev": "e2889e5517600b82905f1d2ba8b70deb71823ffe"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jtolds/gls",
|
||||
"Rev": "f1ac7f4f24f50328e6bc838ca4437d1612a0243c"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/lib/pq",
|
||||
"Comment": "go1.0-cutoff-13-g19eeca3",
|
||||
"Rev": "19eeca3e30d2577b1761db471ec130810e67f532"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/macaron-contrib/binding",
|
||||
"Rev": "0fbe4b9707e6eb556ef843e5471592f55ce0a5e7"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/macaron-contrib/session",
|
||||
"Rev": "31e841d95c7302b9ac456c830ea2d6dfcef4f84a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-sqlite3",
|
||||
"Rev": "e28cd440fabdd39b9520344bc26829f61db40ece"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/smartystreets/goconvey/convey",
|
||||
"Comment": "1.5.0-356-gfbc0a1c",
|
||||
"Rev": "fbc0a1c888f9f96263f9a559d1769905245f1123"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/streadway/amqp",
|
||||
"Rev": "150b7f24d6ad507e6026c13d85ce1f1391ac7400"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/context",
|
||||
"Rev": "972f0c5fbe4ae29e666c3f78c3ed42ae7a448b0a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/oauth2",
|
||||
"Rev": "c58fcf0ffc1c772aa2e1ee4894bc19f2649263b2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/bufio.v1",
|
||||
"Comment": "v1",
|
||||
"Rev": "567b2bfa514e796916c4747494d6ff5132a1dfce"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/ini.v1",
|
||||
"Comment": "v0-16-g1772191",
|
||||
"Rev": "177219109c97e7920c933e21c9b25f874357b237"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkg.in/redis.v2",
|
||||
"Comment": "v2.3.2",
|
||||
"Rev": "e6179049628164864e6e84e973cfb56335748dea"
|
||||
},
|
||||
{
|
||||
"ImportPath": "gopkgs.com/pool.v1",
|
||||
"Rev": "c850f092aad1780cbffff25f471c5cc32097932a"
|
||||
}
|
||||
]
|
||||
}
|
||||
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@@ -0,0 +1,5 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
||||
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/pkg
|
||||
/bin
|
||||
24
Godeps/_workspace/src/github.com/Unknwon/com/README.md
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/Unknwon/com/README.md
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Common functions
|
||||
===
|
||||
|
||||
[](https://drone.io/github.com/Unknwon/com/latest) [](http://gowalker.org/github.com/Unknwon/com)
|
||||
|
||||
This is an open source project for commonly used functions for the Go programming language.
|
||||
|
||||
This package need >= **go 1.2**
|
||||
|
||||
Code Convention: based on [Go Code Convention](https://github.com/Unknwon/go-code-convention).
|
||||
|
||||
## Contribute
|
||||
|
||||
Your contribute is welcome, but you have to check following steps after you added some functions and commit them:
|
||||
|
||||
1. Make sure you wrote user-friendly comments for **all functions** .
|
||||
2. Make sure you wrote test cases with any possible condition for **all functions** in file `*_test.go`.
|
||||
3. Make sure you wrote benchmarks for **all functions** in file `*_test.go`.
|
||||
4. Make sure you wrote useful examples for **all functions** in file `example_test.go`.
|
||||
5. Make sure you ran `go test -bench="."` and got **PASS** .
|
||||
|
||||
## Performance
|
||||
|
||||
See results on [drone.io](https://drone.io/github.com/Unknwon/com/latest) by `go test -bench="."`.
|
||||
140
Godeps/_workspace/src/github.com/Unknwon/com/cmd_test.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/Unknwon/com/cmd_test.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColorLogS(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
// Trace + path.
|
||||
cls := ColorLogS("[TRAC] Trace level test with path( %s )", "/path/to/somethere")
|
||||
clsR := fmt.Sprintf(
|
||||
"[\033[%dmTRAC%s] Trace level test with path(\033[%dm%s%s)",
|
||||
Blue, EndColor, Yellow, "/path/to/somethere", EndColor)
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Error + error.
|
||||
cls = ColorLogS("[ERRO] Error level test with error[ %s ]", "test error")
|
||||
clsR = fmt.Sprintf(
|
||||
"[\033[%dmERRO%s] Error level test with error[\033[%dm%s%s]",
|
||||
Red, EndColor, Red, "test error", EndColor)
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Warning + highlight.
|
||||
cls = ColorLogS("[WARN] Warnning level test with highlight # %s #", "special offer!")
|
||||
clsR = fmt.Sprintf(
|
||||
"[\033[%dmWARN%s] Warnning level test with highlight \033[%dm%s%s",
|
||||
Magenta, EndColor, Gray, "special offer!", EndColor)
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Success.
|
||||
cls = ColorLogS("[SUCC] Success level test")
|
||||
clsR = fmt.Sprintf(
|
||||
"[\033[%dmSUCC%s] Success level test",
|
||||
Green, EndColor)
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Default.
|
||||
cls = ColorLogS("[INFO] Default level test")
|
||||
clsR = fmt.Sprintf(
|
||||
"[INFO] Default level test")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
} else {
|
||||
// Trace + path.
|
||||
cls := ColorLogS("[TRAC] Trace level test with path( %s )", "/path/to/somethere")
|
||||
clsR := fmt.Sprintf(
|
||||
"[TRAC] Trace level test with path(%s)",
|
||||
"/path/to/somethere")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Error + error.
|
||||
cls = ColorLogS("[ERRO] Error level test with error[ %s ]", "test error")
|
||||
clsR = fmt.Sprintf(
|
||||
"[ERRO] Error level test with error[%s]",
|
||||
"test error")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Warning + highlight.
|
||||
cls = ColorLogS("[WARN] Warnning level test with highlight # %s #", "special offer!")
|
||||
clsR = fmt.Sprintf(
|
||||
"[WARN] Warnning level test with highlight %s",
|
||||
"special offer!")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Success.
|
||||
cls = ColorLogS("[SUCC] Success level test")
|
||||
clsR = fmt.Sprintf(
|
||||
"[SUCC] Success level test")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
|
||||
// Default.
|
||||
cls = ColorLogS("[INFO] Default level test")
|
||||
clsR = fmt.Sprintf(
|
||||
"[INFO] Default level test")
|
||||
if cls != clsR {
|
||||
t.Errorf("ColorLogS:\n Expect => %s\n Got => %s\n", clsR, cls)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecCmd(t *testing.T) {
|
||||
stdout, stderr, err := ExecCmd("go", "help", "get")
|
||||
if err != nil {
|
||||
t.Errorf("ExecCmd:\n Expect => %v\n Got => %v\n", nil, err)
|
||||
} else if len(stderr) != 0 {
|
||||
t.Errorf("ExecCmd:\n Expect => %s\n Got => %s\n", "", stderr)
|
||||
} else if !strings.HasPrefix(stdout, "usage: go get") {
|
||||
t.Errorf("ExecCmd:\n Expect => %s\n Got => %s\n", "usage: go get", stdout)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkColorLogS(b *testing.B) {
|
||||
log := fmt.Sprintf(
|
||||
"[WARN] This is a tesing log that should be colored, path( %s ),"+
|
||||
" highlight # %s #, error [ %s ].",
|
||||
"path to somewhere", "highlighted content", "tesing error")
|
||||
for i := 0; i < b.N; i++ {
|
||||
ColorLogS(log)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExecCmd(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
ExecCmd("go", "help", "get")
|
||||
}
|
||||
}
|
||||
157
Godeps/_workspace/src/github.com/Unknwon/com/convert.go
generated
vendored
Normal file
157
Godeps/_workspace/src/github.com/Unknwon/com/convert.go
generated
vendored
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2014 com 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 com
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Convert string to specify type.
|
||||
type StrTo string
|
||||
|
||||
func (f StrTo) Exist() bool {
|
||||
return string(f) != string(0x1E)
|
||||
}
|
||||
|
||||
func (f StrTo) Uint8() (uint8, error) {
|
||||
v, err := strconv.ParseUint(f.String(), 10, 8)
|
||||
return uint8(v), err
|
||||
}
|
||||
|
||||
func (f StrTo) Int() (int, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 32)
|
||||
return int(v), err
|
||||
}
|
||||
|
||||
func (f StrTo) Int64() (int64, error) {
|
||||
v, err := strconv.ParseInt(f.String(), 10, 64)
|
||||
return int64(v), err
|
||||
}
|
||||
|
||||
func (f StrTo) MustUint8() uint8 {
|
||||
v, _ := f.Uint8()
|
||||
return v
|
||||
}
|
||||
|
||||
func (f StrTo) MustInt() int {
|
||||
v, _ := f.Int()
|
||||
return v
|
||||
}
|
||||
|
||||
func (f StrTo) MustInt64() int64 {
|
||||
v, _ := f.Int64()
|
||||
return v
|
||||
}
|
||||
|
||||
func (f StrTo) String() string {
|
||||
if f.Exist() {
|
||||
return string(f)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Convert any type to string.
|
||||
func ToStr(value interface{}, args ...int) (s string) {
|
||||
switch v := value.(type) {
|
||||
case bool:
|
||||
s = strconv.FormatBool(v)
|
||||
case float32:
|
||||
s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
|
||||
case float64:
|
||||
s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
|
||||
case int:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int8:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int16:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int32:
|
||||
s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
|
||||
case int64:
|
||||
s = strconv.FormatInt(v, argInt(args).Get(0, 10))
|
||||
case uint:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint8:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint16:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint32:
|
||||
s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
|
||||
case uint64:
|
||||
s = strconv.FormatUint(v, argInt(args).Get(0, 10))
|
||||
case string:
|
||||
s = v
|
||||
case []byte:
|
||||
s = string(v)
|
||||
default:
|
||||
s = fmt.Sprintf("%v", v)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type argInt []int
|
||||
|
||||
func (a argInt) Get(i int, args ...int) (r int) {
|
||||
if i >= 0 && i < len(a) {
|
||||
r = a[i]
|
||||
} else if len(args) > 0 {
|
||||
r = args[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// HexStr2int converts hex format string to decimal number.
|
||||
func HexStr2int(hexStr string) (int, error) {
|
||||
num := 0
|
||||
length := len(hexStr)
|
||||
for i := 0; i < length; i++ {
|
||||
char := hexStr[length-i-1]
|
||||
factor := -1
|
||||
|
||||
switch {
|
||||
case char >= '0' && char <= '9':
|
||||
factor = int(char) - '0'
|
||||
case char >= 'a' && char <= 'f':
|
||||
factor = int(char) - 'a' + 10
|
||||
default:
|
||||
return -1, fmt.Errorf("invalid hex: %s", string(char))
|
||||
}
|
||||
|
||||
num += factor * PowInt(16, i)
|
||||
}
|
||||
return num, nil
|
||||
}
|
||||
|
||||
// Int2HexStr converts decimal number to hex format string.
|
||||
func Int2HexStr(num int) (hex string) {
|
||||
if num == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
for num > 0 {
|
||||
r := num % 16
|
||||
|
||||
c := "?"
|
||||
if r >= 0 && r <= 9 {
|
||||
c = string(r + '0')
|
||||
} else {
|
||||
c = string(r + 'a' - 10)
|
||||
}
|
||||
hex = c + hex
|
||||
num = num / 16
|
||||
}
|
||||
return hex
|
||||
}
|
||||
56
Godeps/_workspace/src/github.com/Unknwon/com/convert_test.go
generated
vendored
Normal file
56
Godeps/_workspace/src/github.com/Unknwon/com/convert_test.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2014 com 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 com
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestHexStr2int(t *testing.T) {
|
||||
Convey("Convert hex format string to decimal", t, func() {
|
||||
hexDecs := map[string]int{
|
||||
"1": 1,
|
||||
"002": 2,
|
||||
"011": 17,
|
||||
"0a1": 161,
|
||||
"35e": 862,
|
||||
}
|
||||
|
||||
for hex, dec := range hexDecs {
|
||||
val, err := HexStr2int(hex)
|
||||
So(err, ShouldBeNil)
|
||||
So(val, ShouldEqual, dec)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInt2HexStr(t *testing.T) {
|
||||
Convey("Convert decimal to hex format string", t, func() {
|
||||
decHexs := map[int]string{
|
||||
1: "1",
|
||||
2: "2",
|
||||
17: "11",
|
||||
161: "a1",
|
||||
862: "35e",
|
||||
}
|
||||
|
||||
for dec, hex := range decHexs {
|
||||
val := Int2HexStr(dec)
|
||||
So(val, ShouldEqual, hex)
|
||||
}
|
||||
})
|
||||
}
|
||||
173
Godeps/_workspace/src/github.com/Unknwon/com/dir.go
generated
vendored
Normal file
173
Godeps/_workspace/src/github.com/Unknwon/com/dir.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IsDir returns true if given path is a directory,
|
||||
// or returns false when it's a file or does not exist.
|
||||
func IsDir(dir string) bool {
|
||||
f, e := os.Stat(dir)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
return f.IsDir()
|
||||
}
|
||||
|
||||
func statDir(dirPath, recPath string, includeDir, isDirOnly bool) ([]string, error) {
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer dir.Close()
|
||||
|
||||
fis, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
statList := make([]string, 0)
|
||||
for _, fi := range fis {
|
||||
if strings.Contains(fi.Name(), ".DS_Store") {
|
||||
continue
|
||||
}
|
||||
|
||||
relPath := path.Join(recPath, fi.Name())
|
||||
curPath := path.Join(dirPath, fi.Name())
|
||||
if fi.IsDir() {
|
||||
if includeDir {
|
||||
statList = append(statList, relPath+"/")
|
||||
}
|
||||
s, err := statDir(curPath, relPath, includeDir, isDirOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
statList = append(statList, s...)
|
||||
} else if !isDirOnly {
|
||||
statList = append(statList, relPath)
|
||||
}
|
||||
}
|
||||
return statList, nil
|
||||
}
|
||||
|
||||
// StatDir gathers information of given directory by depth-first.
|
||||
// It returns slice of file list and includes subdirectories if enabled;
|
||||
// it returns error and nil slice when error occurs in underlying functions,
|
||||
// or given path is not a directory or does not exist.
|
||||
//
|
||||
// Slice does not include given path itself.
|
||||
// If subdirectories is enabled, they will have suffix '/'.
|
||||
func StatDir(rootPath string, includeDir ...bool) ([]string, error) {
|
||||
if !IsDir(rootPath) {
|
||||
return nil, errors.New("not a directory or does not exist: " + rootPath)
|
||||
}
|
||||
|
||||
isIncludeDir := false
|
||||
if len(includeDir) >= 1 {
|
||||
isIncludeDir = includeDir[0]
|
||||
}
|
||||
return statDir(rootPath, "", isIncludeDir, false)
|
||||
}
|
||||
|
||||
// GetAllSubDirs returns all subdirectories of given root path.
|
||||
// Slice does not include given path itself.
|
||||
func GetAllSubDirs(rootPath string) ([]string, error) {
|
||||
if !IsDir(rootPath) {
|
||||
return nil, errors.New("not a directory or does not exist: " + rootPath)
|
||||
}
|
||||
return statDir(rootPath, "", true, true)
|
||||
}
|
||||
|
||||
// GetFileListBySuffix returns an ordered list of file paths.
|
||||
// It recognize if given path is a file, and don't do recursive find.
|
||||
func GetFileListBySuffix(dirPath, suffix string) ([]string, error) {
|
||||
if !IsExist(dirPath) {
|
||||
return nil, fmt.Errorf("given path does not exist: %s", dirPath)
|
||||
} else if IsFile(dirPath) {
|
||||
return []string{dirPath}, nil
|
||||
}
|
||||
|
||||
// Given path is a directory.
|
||||
dir, err := os.Open(dirPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fis, err := dir.Readdir(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files := make([]string, 0, len(fis))
|
||||
for _, fi := range fis {
|
||||
if strings.HasSuffix(fi.Name(), suffix) {
|
||||
files = append(files, path.Join(dirPath, fi.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
return files, nil
|
||||
}
|
||||
|
||||
// CopyDir copy files recursively from source to target directory.
|
||||
//
|
||||
// The filter accepts a function that process the path info.
|
||||
// and should return true for need to filter.
|
||||
//
|
||||
// It returns error when error occurs in underlying functions.
|
||||
func CopyDir(srcPath, destPath string, filters ...func(filePath string) bool) error {
|
||||
// Check if target directory exists.
|
||||
if IsExist(destPath) {
|
||||
return errors.New("file or directory alreay exists: " + destPath)
|
||||
}
|
||||
|
||||
err := os.MkdirAll(destPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Gather directory info.
|
||||
infos, err := StatDir(srcPath, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var filter func(filePath string) bool
|
||||
if len(filters) > 0 {
|
||||
filter = filters[0]
|
||||
}
|
||||
|
||||
for _, info := range infos {
|
||||
if filter != nil && filter(info) {
|
||||
continue
|
||||
}
|
||||
|
||||
curPath := path.Join(destPath, info)
|
||||
if strings.HasSuffix(info, "/") {
|
||||
err = os.MkdirAll(curPath, os.ModePerm)
|
||||
} else {
|
||||
err = Copy(path.Join(srcPath, info), curPath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
58
Godeps/_workspace/src/github.com/Unknwon/com/dir_test.go
generated
vendored
Normal file
58
Godeps/_workspace/src/github.com/Unknwon/com/dir_test.go
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestIsDir(t *testing.T) {
|
||||
Convey("Check if given path is a directory", t, func() {
|
||||
Convey("Pass a file name", func() {
|
||||
So(IsDir("file.go"), ShouldEqual, false)
|
||||
})
|
||||
Convey("Pass a directory name", func() {
|
||||
So(IsDir("testdata"), ShouldEqual, true)
|
||||
})
|
||||
Convey("Pass a invalid path", func() {
|
||||
So(IsDir("foo"), ShouldEqual, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCopyDir(t *testing.T) {
|
||||
Convey("Items of two slices should be same", t, func() {
|
||||
s1, err := StatDir("testdata", true)
|
||||
So(err, ShouldEqual, nil)
|
||||
|
||||
err = CopyDir("testdata", "testdata2")
|
||||
So(err, ShouldEqual, nil)
|
||||
|
||||
s2, err := StatDir("testdata2", true)
|
||||
os.RemoveAll("testdata2")
|
||||
So(err, ShouldEqual, nil)
|
||||
|
||||
So(CompareSliceStr(s1, s2), ShouldEqual, true)
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIsDir(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsDir("file.go")
|
||||
}
|
||||
}
|
||||
299
Godeps/_workspace/src/github.com/Unknwon/com/example_test.go
generated
vendored
Normal file
299
Godeps/_workspace/src/github.com/Unknwon/com/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,299 @@
|
||||
// Copyright 2013 com 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 com_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
// ------------------------------
|
||||
// cmd.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleColorLogS() {
|
||||
coloredLog := com.ColorLogS(fmt.Sprintf(
|
||||
"[WARN] This is a tesing log that should be colored, path( %s ),"+
|
||||
" highlight # %s #, error [ %s ].",
|
||||
"path to somewhere", "highlighted content", "tesing error"))
|
||||
fmt.Println(coloredLog)
|
||||
}
|
||||
|
||||
func ExampleColorLog() {
|
||||
com.ColorLog(fmt.Sprintf(
|
||||
"[WARN] This is a tesing log that should be colored, path( %s ),"+
|
||||
" highlight # %s #, error [ %s ].",
|
||||
"path to somewhere", "highlighted content", "tesing error"))
|
||||
}
|
||||
|
||||
func ExampleExecCmd() {
|
||||
stdout, stderr, err := com.ExecCmd("go", "help", "get")
|
||||
fmt.Println(stdout, stderr, err)
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// html.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleHtml2JS() {
|
||||
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
|
||||
js := string(com.Html2JS([]byte(htm)))
|
||||
fmt.Println(js)
|
||||
// Output: <div id=\"button\" class=\"btn\">Click me</div>\n
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// path.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleGetGOPATHs() {
|
||||
gps := com.GetGOPATHs()
|
||||
fmt.Println(gps)
|
||||
}
|
||||
|
||||
func ExampleGetSrcPath() {
|
||||
srcPath, err := com.GetSrcPath("github.com/Unknwon/com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
fmt.Println(srcPath)
|
||||
}
|
||||
|
||||
func ExampleHomeDir() {
|
||||
hd, err := com.HomeDir()
|
||||
fmt.Println(hd, err)
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// file.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleIsFile() {
|
||||
if com.IsFile("file.go") {
|
||||
fmt.Println("file.go exists")
|
||||
return
|
||||
}
|
||||
fmt.Println("file.go is not a file or does not exist")
|
||||
}
|
||||
|
||||
func ExampleIsExist() {
|
||||
if com.IsExist("file.go") {
|
||||
fmt.Println("file.go exists")
|
||||
return
|
||||
}
|
||||
fmt.Println("file.go does not exist")
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// dir.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleIsDir() {
|
||||
if com.IsDir("files") {
|
||||
fmt.Println("directory 'files' exists")
|
||||
return
|
||||
}
|
||||
fmt.Println("'files' is not a directory or does not exist")
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// string.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleIsLetter() {
|
||||
fmt.Println(com.IsLetter('1'))
|
||||
fmt.Println(com.IsLetter('['))
|
||||
fmt.Println(com.IsLetter('a'))
|
||||
fmt.Println(com.IsLetter('Z'))
|
||||
// Output:
|
||||
// false
|
||||
// false
|
||||
// true
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleExpand() {
|
||||
match := map[string]string{
|
||||
"domain": "gowalker.org",
|
||||
"subdomain": "github.com",
|
||||
}
|
||||
s := "http://{domain}/{subdomain}/{0}/{1}"
|
||||
fmt.Println(com.Expand(s, match, "Unknwon", "gowalker"))
|
||||
// Output: http://gowalker.org/github.com/Unknwon/gowalker
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// http.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleHttpGet() ([]byte, error) {
|
||||
rc, err := com.HttpGet(&http.Client{}, "http://gowalker.org", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p, err := ioutil.ReadAll(rc)
|
||||
rc.Close()
|
||||
return p, err
|
||||
}
|
||||
|
||||
func ExampleHttpGetBytes() ([]byte, error) {
|
||||
p, err := com.HttpGetBytes(&http.Client{}, "http://gowalker.org", nil)
|
||||
return p, err
|
||||
}
|
||||
|
||||
func ExampleHttpGetJSON() interface{} {
|
||||
j := com.HttpGetJSON(&http.Client{}, "http://gowalker.org", nil)
|
||||
return j
|
||||
}
|
||||
|
||||
type rawFile struct {
|
||||
name string
|
||||
rawURL string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (rf *rawFile) Name() string {
|
||||
return rf.name
|
||||
}
|
||||
|
||||
func (rf *rawFile) RawUrl() string {
|
||||
return rf.rawURL
|
||||
}
|
||||
|
||||
func (rf *rawFile) Data() []byte {
|
||||
return rf.data
|
||||
}
|
||||
|
||||
func (rf *rawFile) SetData(p []byte) {
|
||||
rf.data = p
|
||||
}
|
||||
|
||||
func ExampleFetchFiles() {
|
||||
// Code that should be outside of your function body.
|
||||
// type rawFile struct {
|
||||
// name string
|
||||
// rawURL string
|
||||
// data []byte
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) Name() string {
|
||||
// return rf.name
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) RawUrl() string {
|
||||
// return rf.rawURL
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) Data() []byte {
|
||||
// return rf.data
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) SetData(p []byte) {
|
||||
// rf.data = p
|
||||
// }
|
||||
|
||||
files := []com.RawFile{
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
&rawFile{rawURL: "http://example.com/foo"},
|
||||
}
|
||||
err := com.FetchFiles(&http.Client{}, files, nil)
|
||||
fmt.Println(err, len(files[0].Data()), len(files[1].Data()))
|
||||
}
|
||||
|
||||
func ExampleFetchFilesCurl() {
|
||||
// Code that should be outside of your function body.
|
||||
// type rawFile struct {
|
||||
// name string
|
||||
// rawURL string
|
||||
// data []byte
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) Name() string {
|
||||
// return rf.name
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) RawUrl() string {
|
||||
// return rf.rawURL
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) Data() []byte {
|
||||
// return rf.data
|
||||
// }
|
||||
|
||||
// func (rf *rawFile) SetData(p []byte) {
|
||||
// rf.data = p
|
||||
// }
|
||||
|
||||
files := []com.RawFile{
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
&rawFile{rawURL: "http://example.com/foo"},
|
||||
}
|
||||
err := com.FetchFilesCurl(files)
|
||||
fmt.Println(err, len(files[0].Data()), len(files[1].Data()))
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// regex.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleIsEmail() {
|
||||
fmt.Println(com.IsEmail("test@example.com"))
|
||||
fmt.Println(com.IsEmail("@example.com"))
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
func ExampleIsUrl() {
|
||||
fmt.Println(com.IsUrl("http://example.com"))
|
||||
fmt.Println(com.IsUrl("http//example.com"))
|
||||
// Output:
|
||||
// true
|
||||
// false
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
|
||||
// ------------------------------
|
||||
// slice.go
|
||||
// ------------------------------
|
||||
|
||||
func ExampleAppendStr() {
|
||||
s := []string{"a"}
|
||||
s = com.AppendStr(s, "a")
|
||||
s = com.AppendStr(s, "b")
|
||||
fmt.Println(s)
|
||||
// Output: [a b]
|
||||
}
|
||||
|
||||
// ------------- END ------------
|
||||
61
Godeps/_workspace/src/github.com/Unknwon/com/file_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/Unknwon/com/file_test.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestIsFile(t *testing.T) {
|
||||
if !IsFile("file.go") {
|
||||
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", true, false)
|
||||
}
|
||||
|
||||
if IsFile("testdata") {
|
||||
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", false, true)
|
||||
}
|
||||
|
||||
if IsFile("files.go") {
|
||||
t.Errorf("IsExist:\n Expect => %v\n Got => %v\n", false, true)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsExist(t *testing.T) {
|
||||
Convey("Check if file or directory exists", t, func() {
|
||||
Convey("Pass a file name that exists", func() {
|
||||
So(IsExist("file.go"), ShouldEqual, true)
|
||||
})
|
||||
Convey("Pass a directory name that exists", func() {
|
||||
So(IsExist("testdata"), ShouldEqual, true)
|
||||
})
|
||||
Convey("Pass a directory name that does not exist", func() {
|
||||
So(IsExist(".hg"), ShouldEqual, false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIsFile(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsFile("file.go")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsExist(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsExist("file.go")
|
||||
}
|
||||
}
|
||||
35
Godeps/_workspace/src/github.com/Unknwon/com/html_test.go
generated
vendored
Normal file
35
Godeps/_workspace/src/github.com/Unknwon/com/html_test.go
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHtml2JS(t *testing.T) {
|
||||
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
|
||||
js := string(Html2JS([]byte(htm)))
|
||||
jsR := `<div id=\"button\" class=\"btn\">Click me</div>\n`
|
||||
if js != jsR {
|
||||
t.Errorf("Html2JS:\n Expect => %s\n Got => %s\n", jsR, js)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHtml2JS(b *testing.B) {
|
||||
htm := "<div id=\"button\" class=\"btn\">Click me</div>\n\r"
|
||||
for i := 0; i < b.N; i++ {
|
||||
Html2JS([]byte(htm))
|
||||
}
|
||||
}
|
||||
111
Godeps/_workspace/src/github.com/Unknwon/com/http_test.go
generated
vendored
Normal file
111
Godeps/_workspace/src/github.com/Unknwon/com/http_test.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var examplePrefix = `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Example Domain</title>
|
||||
`
|
||||
|
||||
func TestHttpGet(t *testing.T) {
|
||||
// 200.
|
||||
rc, err := HttpGet(&http.Client{}, "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("HttpGet:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
}
|
||||
p, err := ioutil.ReadAll(rc)
|
||||
if err != nil {
|
||||
t.Errorf("HttpGet:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
}
|
||||
s := string(p)
|
||||
if !strings.HasPrefix(s, examplePrefix) {
|
||||
t.Errorf("HttpGet:\n Expect => %s\n Got => %s\n", examplePrefix, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpGetBytes(t *testing.T) {
|
||||
p, err := HttpGetBytes(&http.Client{}, "http://example.com", nil)
|
||||
if err != nil {
|
||||
t.Errorf("HttpGetBytes:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
}
|
||||
s := string(p)
|
||||
if !strings.HasPrefix(s, examplePrefix) {
|
||||
t.Errorf("HttpGet:\n Expect => %s\n Got => %s\n", examplePrefix, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHttpGetJSON(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
type rawFile struct {
|
||||
name string
|
||||
rawURL string
|
||||
data []byte
|
||||
}
|
||||
|
||||
func (rf *rawFile) Name() string {
|
||||
return rf.name
|
||||
}
|
||||
|
||||
func (rf *rawFile) RawUrl() string {
|
||||
return rf.rawURL
|
||||
}
|
||||
|
||||
func (rf *rawFile) Data() []byte {
|
||||
return rf.data
|
||||
}
|
||||
|
||||
func (rf *rawFile) SetData(p []byte) {
|
||||
rf.data = p
|
||||
}
|
||||
|
||||
func TestFetchFiles(t *testing.T) {
|
||||
files := []RawFile{
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
}
|
||||
err := FetchFiles(&http.Client{}, files, nil)
|
||||
if err != nil {
|
||||
t.Errorf("FetchFiles:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
} else if len(files[0].Data()) != 1270 {
|
||||
t.Errorf("FetchFiles:\n Expect => %d\n Got => %d\n", 1270, len(files[0].Data()))
|
||||
} else if len(files[1].Data()) != 1270 {
|
||||
t.Errorf("FetchFiles:\n Expect => %d\n Got => %d\n", 1270, len(files[1].Data()))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchFilesCurl(t *testing.T) {
|
||||
files := []RawFile{
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
&rawFile{rawURL: "http://example.com"},
|
||||
}
|
||||
err := FetchFilesCurl(files)
|
||||
if err != nil {
|
||||
t.Errorf("FetchFilesCurl:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
} else if len(files[0].Data()) != 1270 {
|
||||
t.Errorf("FetchFilesCurl:\n Expect => %d\n Got => %d\n", 1270, len(files[0].Data()))
|
||||
} else if len(files[1].Data()) != 1270 {
|
||||
t.Errorf("FetchFilesCurl:\n Expect => %d\n Got => %d\n", 1270, len(files[1].Data()))
|
||||
}
|
||||
}
|
||||
24
Godeps/_workspace/src/github.com/Unknwon/com/math.go
generated
vendored
Normal file
24
Godeps/_workspace/src/github.com/Unknwon/com/math.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2014 com 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 com
|
||||
|
||||
// PowInt is int type of math.Pow function.
|
||||
func PowInt(x int, y int) int {
|
||||
num := 1
|
||||
for i := 0; i < y; i++ {
|
||||
num *= x
|
||||
}
|
||||
return num
|
||||
}
|
||||
80
Godeps/_workspace/src/github.com/Unknwon/com/path.go
generated
vendored
Normal file
80
Godeps/_workspace/src/github.com/Unknwon/com/path.go
generated
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetGOPATHs returns all paths in GOPATH variable.
|
||||
func GetGOPATHs() []string {
|
||||
gopath := os.Getenv("GOPATH")
|
||||
var paths []string
|
||||
if runtime.GOOS == "windows" {
|
||||
gopath = strings.Replace(gopath, "\\", "/", -1)
|
||||
paths = strings.Split(gopath, ";")
|
||||
} else {
|
||||
paths = strings.Split(gopath, ":")
|
||||
}
|
||||
return paths
|
||||
}
|
||||
|
||||
// GetSrcPath returns app. source code path.
|
||||
// It only works when you have src. folder in GOPATH,
|
||||
// it returns error not able to locate source folder path.
|
||||
func GetSrcPath(importPath string) (appPath string, err error) {
|
||||
paths := GetGOPATHs()
|
||||
for _, p := range paths {
|
||||
if IsExist(p + "/src/" + importPath + "/") {
|
||||
appPath = p + "/src/" + importPath + "/"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(appPath) == 0 {
|
||||
return "", errors.New("Unable to locate source folder path")
|
||||
}
|
||||
|
||||
appPath = filepath.Dir(appPath) + "/"
|
||||
if runtime.GOOS == "windows" {
|
||||
// Replace all '\' to '/'.
|
||||
appPath = strings.Replace(appPath, "\\", "/", -1)
|
||||
}
|
||||
|
||||
return appPath, nil
|
||||
}
|
||||
|
||||
// HomeDir returns path of '~'(in Linux) on Windows,
|
||||
// it returns error when the variable does not exist.
|
||||
func HomeDir() (home string, err error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
|
||||
if home == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
} else {
|
||||
home = os.Getenv("HOME")
|
||||
}
|
||||
|
||||
if len(home) == 0 {
|
||||
return "", errors.New("Cannot specify home directory because it's empty")
|
||||
}
|
||||
|
||||
return home, nil
|
||||
}
|
||||
67
Godeps/_workspace/src/github.com/Unknwon/com/path_test.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/Unknwon/com/path_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetGOPATHs(t *testing.T) {
|
||||
var gpsR []string
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
gpsR = []string{"path/to/gopath1", "path/to/gopath2", "path/to/gopath3"}
|
||||
os.Setenv("GOPATH", "path/to/gopath1:path/to/gopath2:path/to/gopath3")
|
||||
} else {
|
||||
gpsR = []string{"path/to/gopath1", "path/to/gopath2", "path/to/gopath3"}
|
||||
os.Setenv("GOPATH", "path\\to\\gopath1;path\\to\\gopath2;path\\to\\gopath3")
|
||||
}
|
||||
|
||||
gps := GetGOPATHs()
|
||||
if !CompareSliceStr(gps, gpsR) {
|
||||
t.Errorf("GetGOPATHs:\n Expect => %s\n Got => %s\n", gpsR, gps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSrcPath(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestHomeDir(t *testing.T) {
|
||||
_, err := HomeDir()
|
||||
if err != nil {
|
||||
t.Errorf("HomeDir:\n Expect => %v\n Got => %s\n", nil, err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetGOPATHs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
GetGOPATHs()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGetSrcPath(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
GetSrcPath("github.com/Unknwon/com")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHomeDir(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
HomeDir()
|
||||
}
|
||||
}
|
||||
70
Godeps/_workspace/src/github.com/Unknwon/com/regex_test.go
generated
vendored
Normal file
70
Godeps/_workspace/src/github.com/Unknwon/com/regex_test.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsEmail(t *testing.T) {
|
||||
emails := map[string]bool{
|
||||
`test@example.com`: true,
|
||||
`single-character@b.org`: true,
|
||||
`uncommon_address@test.museum`: true,
|
||||
`local@sld.UPPER`: true,
|
||||
`@missing.org`: false,
|
||||
`missing@.com`: false,
|
||||
`missing@qq.`: false,
|
||||
`wrong-ip@127.1.1.1.26`: false,
|
||||
}
|
||||
for e, r := range emails {
|
||||
b := IsEmail(e)
|
||||
if b != r {
|
||||
t.Errorf("IsEmail:\n Expect => %v\n Got => %v\n", r, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUrl(t *testing.T) {
|
||||
urls := map[string]bool{
|
||||
"http://www.example.com": true,
|
||||
"http://example.com": true,
|
||||
"http://example.com?user=test&password=test": true,
|
||||
"http://example.com?user=test#login": true,
|
||||
"ftp://example.com": true,
|
||||
"https://example.com": true,
|
||||
"htp://example.com": false,
|
||||
"http//example.com": false,
|
||||
"http://example": true,
|
||||
}
|
||||
for u, r := range urls {
|
||||
b := IsUrl(u)
|
||||
if b != r {
|
||||
t.Errorf("IsUrl:\n Expect => %v\n Got => %v\n", r, b)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsEmail(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsEmail("test@example.com")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsUrl(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsEmail("http://example.com")
|
||||
}
|
||||
}
|
||||
99
Godeps/_workspace/src/github.com/Unknwon/com/slice_test.go
generated
vendored
Normal file
99
Godeps/_workspace/src/github.com/Unknwon/com/slice_test.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func TestAppendStr(t *testing.T) {
|
||||
Convey("Append a string to a slice with no duplicates", t, func() {
|
||||
s := []string{"a"}
|
||||
|
||||
Convey("Append a string that does not exist in slice", func() {
|
||||
s = AppendStr(s, "b")
|
||||
So(len(s), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Append a string that does exist in slice", func() {
|
||||
s = AppendStr(s, "b")
|
||||
So(len(s), ShouldEqual, 2)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompareSliceStr(t *testing.T) {
|
||||
Convey("Compares two 'string' type slices with elements and order", t, func() {
|
||||
Convey("Compare two slices that do have same elements and order", func() {
|
||||
So(CompareSliceStr(
|
||||
[]string{"1", "2", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Compare two slices that do have same elements but does not have same order", func() {
|
||||
So(!CompareSliceStr(
|
||||
[]string{"2", "1", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Compare two slices that have different number of elements", func() {
|
||||
So(!CompareSliceStr(
|
||||
[]string{"2", "1"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompareSliceStrU(t *testing.T) {
|
||||
Convey("Compare two 'string' type slices with elements and ignore the order", t, func() {
|
||||
Convey("Compare two slices that do have same elements and order", func() {
|
||||
So(CompareSliceStrU(
|
||||
[]string{"1", "2", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Compare two slices that do have same elements but does not have same order", func() {
|
||||
So(CompareSliceStrU(
|
||||
[]string{"2", "1", "3"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Compare two slices that have different number of elements", func() {
|
||||
So(!CompareSliceStrU(
|
||||
[]string{"2", "1"}, []string{"1", "2", "3"}), ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkAppendStr(b *testing.B) {
|
||||
s := []string{"a"}
|
||||
for i := 0; i < b.N; i++ {
|
||||
s = AppendStr(s, fmt.Sprint(b.N%3))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCompareSliceStr(b *testing.B) {
|
||||
s1 := []string{"1", "2", "3"}
|
||||
s2 := []string{"1", "2", "3"}
|
||||
for i := 0; i < b.N; i++ {
|
||||
CompareSliceStr(s1, s2)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkCompareSliceStrU(b *testing.B) {
|
||||
s1 := []string{"1", "4", "2", "3"}
|
||||
s2 := []string{"1", "2", "3", "4"}
|
||||
for i := 0; i < b.N; i++ {
|
||||
CompareSliceStrU(s1, s2)
|
||||
}
|
||||
}
|
||||
140
Godeps/_workspace/src/github.com/Unknwon/com/string.go
generated
vendored
Normal file
140
Godeps/_workspace/src/github.com/Unknwon/com/string.go
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"io"
|
||||
r "math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// AESEncrypt encrypts text and given key with AES.
|
||||
func AESEncrypt(key, text []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b := base64.StdEncoding.EncodeToString(text)
|
||||
ciphertext := make([]byte, aes.BlockSize+len(b))
|
||||
iv := ciphertext[:aes.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfb := cipher.NewCFBEncrypter(block, iv)
|
||||
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
|
||||
return ciphertext, nil
|
||||
}
|
||||
|
||||
// AESDecrypt decrypts text and given key with AES.
|
||||
func AESDecrypt(key, text []byte) ([]byte, error) {
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(text) < aes.BlockSize {
|
||||
return nil, errors.New("ciphertext too short")
|
||||
}
|
||||
iv := text[:aes.BlockSize]
|
||||
text = text[aes.BlockSize:]
|
||||
cfb := cipher.NewCFBDecrypter(block, iv)
|
||||
cfb.XORKeyStream(text, text)
|
||||
data, err := base64.StdEncoding.DecodeString(string(text))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// IsLetter returns true if the 'l' is an English letter.
|
||||
func IsLetter(l uint8) bool {
|
||||
n := (l | 0x20) - 'a'
|
||||
if n >= 0 && n < 26 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
|
||||
func Expand(template string, match map[string]string, subs ...string) string {
|
||||
var p []byte
|
||||
var i int
|
||||
for {
|
||||
i = strings.Index(template, "{")
|
||||
if i < 0 {
|
||||
break
|
||||
}
|
||||
p = append(p, template[:i]...)
|
||||
template = template[i+1:]
|
||||
i = strings.Index(template, "}")
|
||||
if s, ok := match[template[:i]]; ok {
|
||||
p = append(p, s...)
|
||||
} else {
|
||||
j, _ := strconv.Atoi(template[:i])
|
||||
if j >= len(subs) {
|
||||
p = append(p, []byte("Missing")...)
|
||||
} else {
|
||||
p = append(p, subs[j]...)
|
||||
}
|
||||
}
|
||||
template = template[i+1:]
|
||||
}
|
||||
p = append(p, template...)
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// Reverse s string, support unicode
|
||||
func Reverse(s string) string {
|
||||
n := len(s)
|
||||
runes := make([]rune, n)
|
||||
for _, rune := range s {
|
||||
n--
|
||||
runes[n] = rune
|
||||
}
|
||||
return string(runes[n:])
|
||||
}
|
||||
|
||||
// RandomCreateBytes generate random []byte by specify chars.
|
||||
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
var bytes = make([]byte, n)
|
||||
var randby bool
|
||||
if num, err := rand.Read(bytes); num != n || err != nil {
|
||||
r.Seed(time.Now().UnixNano())
|
||||
randby = true
|
||||
}
|
||||
for i, b := range bytes {
|
||||
if len(alphabets) == 0 {
|
||||
if randby {
|
||||
bytes[i] = alphanum[r.Intn(len(alphanum))]
|
||||
} else {
|
||||
bytes[i] = alphanum[b%byte(len(alphanum))]
|
||||
}
|
||||
} else {
|
||||
if randby {
|
||||
bytes[i] = alphabets[r.Intn(len(alphabets))]
|
||||
} else {
|
||||
bytes[i] = alphabets[b%byte(len(alphabets))]
|
||||
}
|
||||
}
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
82
Godeps/_workspace/src/github.com/Unknwon/com/string_test.go
generated
vendored
Normal file
82
Godeps/_workspace/src/github.com/Unknwon/com/string_test.go
generated
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2013 com 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 com
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsLetter(t *testing.T) {
|
||||
if IsLetter('1') {
|
||||
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", false, true)
|
||||
}
|
||||
|
||||
if IsLetter('[') {
|
||||
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", false, true)
|
||||
}
|
||||
|
||||
if !IsLetter('a') {
|
||||
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", true, false)
|
||||
}
|
||||
|
||||
if !IsLetter('Z') {
|
||||
t.Errorf("IsLetter:\n Expect => %v\n Got => %v\n", true, false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpand(t *testing.T) {
|
||||
match := map[string]string{
|
||||
"domain": "gowalker.org",
|
||||
"subdomain": "github.com",
|
||||
}
|
||||
s := "http://{domain}/{subdomain}/{0}/{1}"
|
||||
sR := "http://gowalker.org/github.com/Unknwon/gowalker"
|
||||
if Expand(s, match, "Unknwon", "gowalker") != sR {
|
||||
t.Errorf("Expand:\n Expect => %s\n Got => %s\n", sR, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReverse(t *testing.T) {
|
||||
if Reverse("abcdefg") != "gfedcba" {
|
||||
t.Errorf("Reverse:\n Except => %s\n Got =>%s\n", "gfedcba", Reverse("abcdefg"))
|
||||
}
|
||||
if Reverse("上善若水厚德载物") != "物载德厚水若善上" {
|
||||
t.Errorf("Reverse:\n Except => %s\n Got =>%s\n", "物载德厚水若善上", Reverse("上善若水厚德载物"))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsLetter(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsLetter('a')
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkExpand(b *testing.B) {
|
||||
match := map[string]string{
|
||||
"domain": "gowalker.org",
|
||||
"subdomain": "github.com",
|
||||
}
|
||||
s := "http://{domain}/{subdomain}/{0}/{1}"
|
||||
for i := 0; i < b.N; i++ {
|
||||
Expand(s, match, "Unknwon", "gowalker")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReverse(b *testing.B) {
|
||||
s := "abscef中文"
|
||||
for i := 0; i < b.N; i++ {
|
||||
Reverse(s)
|
||||
}
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/SaveFile.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/SaveFile.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFile
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/SaveFileS.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/SaveFileS.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFileS
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/SaveFile.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/SaveFile.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFile
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/SaveFileS.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/SaveFileS.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFileS
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/secondLevel/SaveFile.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/secondLevel/SaveFile.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFile
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/secondLevel/SaveFileS.txt
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/com/testdata/statDir/secondLevel/SaveFileS.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
TestSaveFileS
|
||||
2
Godeps/_workspace/src/github.com/Unknwon/macaron/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/src/github.com/Unknwon/macaron/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
macaron.sublime-project
|
||||
macaron.sublime-workspace
|
||||
94
Godeps/_workspace/src/github.com/Unknwon/macaron/README.md
generated
vendored
Normal file
94
Godeps/_workspace/src/github.com/Unknwon/macaron/README.md
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
Macaron [](https://drone.io/github.com/Unknwon/macaron/latest) [](http://gocover.io/github.com/Unknwon/macaron)
|
||||
=======================
|
||||
|
||||

|
||||
|
||||
Package macaron is a high productive and modular design web framework in Go.
|
||||
|
||||
##### Current version: 0.5.4
|
||||
|
||||
## Getting Started
|
||||
|
||||
To install Macaron:
|
||||
|
||||
go get github.com/Unknwon/macaron
|
||||
|
||||
The very basic usage of Macaron:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/Unknwon/macaron"
|
||||
|
||||
func main() {
|
||||
m := macaron.Classic()
|
||||
m.Get("/", func() string {
|
||||
return "Hello world!"
|
||||
})
|
||||
m.Run()
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Powerful routing with suburl.
|
||||
- Flexible routes combinations.
|
||||
- Unlimited nested group routers.
|
||||
- Directly integrate with existing services.
|
||||
- Dynamically change template files at runtime.
|
||||
- Allow to use in-memory template and static files.
|
||||
- Easy to plugin/unplugin features with modular design.
|
||||
- Handy dependency injection powered by [inject](https://github.com/codegangsta/inject).
|
||||
- Better router layer and less reflection make faster speed.
|
||||
|
||||
## Middlewares
|
||||
|
||||
Middlewares allow you easily plugin/unplugin features for your Macaron applications.
|
||||
|
||||
There are already many [middlewares](https://github.com/macaron-contrib) to simplify your work:
|
||||
|
||||
- gzip - Gzip compression to all requests
|
||||
- render - Go template engine
|
||||
- static - Serves static files
|
||||
- [binding](https://github.com/macaron-contrib/binding) - Request data binding and validation
|
||||
- [i18n](https://github.com/macaron-contrib/i18n) - Internationalization and Localization
|
||||
- [cache](https://github.com/macaron-contrib/cache) - Cache manager
|
||||
- [session](https://github.com/macaron-contrib/session) - Session manager
|
||||
- [csrf](https://github.com/macaron-contrib/csrf) - Generates and validates csrf tokens
|
||||
- [captcha](https://github.com/macaron-contrib/captcha) - Captcha service
|
||||
- [pongo2](https://github.com/macaron-contrib/pongo2) - Pongo2 template engine support
|
||||
- [sockets](https://github.com/macaron-contrib/sockets) - WebSockets channels binding
|
||||
- [bindata](https://github.com/macaron-contrib/bindata) - Embed binary data as static and template files
|
||||
- [toolbox](https://github.com/macaron-contrib/toolbox) - Health check, pprof, profile and statistic services
|
||||
- [oauth2](https://github.com/macaron-contrib/oauth2) - OAuth 2.0 backend
|
||||
- [switcher](https://github.com/macaron-contrib/switcher) - Multiple-site support
|
||||
- [method](https://github.com/macaron-contrib/method) - HTTP method override
|
||||
- [permissions2](https://github.com/xyproto/permissions2) - Cookies, users and permissions
|
||||
- [renders](https://github.com/macaron-contrib/renders) - Beego-like render engine(Macaron has built-in template engine, this is another option)
|
||||
|
||||
## Use Cases
|
||||
|
||||
- [Gogs](https://github.com/gogits/gogs): Go Git Service
|
||||
- [Gogs Web](https://github.com/gogits/gogsweb): Gogs official website
|
||||
- [Go Walker](https://gowalker.org): Go online API documentation
|
||||
- [Switch](https://github.com/gpmgo/switch): Gopm registry
|
||||
- [YouGam](http://yougam.com): Online Forum
|
||||
- [Car Girl](http://qcnl.gzsy.com/): Online campaign
|
||||
- [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc.
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [API Reference](https://gowalker.org/github.com/Unknwon/macaron)
|
||||
- [Documentation](http://macaron.gogs.io)
|
||||
- [FAQs](http://macaron.gogs.io/docs/faqs)
|
||||
- [](https://gitter.im/Unknwon/macaron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
## Credits
|
||||
|
||||
- Basic design of [Martini](https://github.com/go-martini/martini).
|
||||
- Router layer of [beego](https://github.com/astaxie/beego).
|
||||
- Logo is modified by [@insionng](https://github.com/insionng) based on [Tribal Dragon](http://xtremeyamazaki.deviantart.com/art/Tribal-Dragon-27005087).
|
||||
|
||||
## License
|
||||
|
||||
This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
478
Godeps/_workspace/src/github.com/Unknwon/macaron/context.go
generated
vendored
Normal file
478
Godeps/_workspace/src/github.com/Unknwon/macaron/context.go
generated
vendored
Normal file
@@ -0,0 +1,478 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
"github.com/Unknwon/macaron/inject"
|
||||
)
|
||||
|
||||
// Locale reprents a localization interface.
|
||||
type Locale interface {
|
||||
Language() string
|
||||
Tr(string, ...interface{}) string
|
||||
}
|
||||
|
||||
// RequestBody represents a request body.
|
||||
type RequestBody struct {
|
||||
reader io.ReadCloser
|
||||
}
|
||||
|
||||
// Bytes reads and returns content of request body in bytes.
|
||||
func (rb *RequestBody) Bytes() ([]byte, error) {
|
||||
return ioutil.ReadAll(rb.reader)
|
||||
}
|
||||
|
||||
// String reads and returns content of request body in string.
|
||||
func (rb *RequestBody) String() (string, error) {
|
||||
data, err := rb.Bytes()
|
||||
return string(data), err
|
||||
}
|
||||
|
||||
// ReadCloser returns a ReadCloser for request body.
|
||||
func (rb *RequestBody) ReadCloser() io.ReadCloser {
|
||||
return rb.reader
|
||||
}
|
||||
|
||||
// Request represents an HTTP request received by a server or to be sent by a client.
|
||||
type Request struct {
|
||||
*http.Request
|
||||
}
|
||||
|
||||
func (r *Request) Body() *RequestBody {
|
||||
return &RequestBody{r.Request.Body}
|
||||
}
|
||||
|
||||
// Context represents the runtime context of current request of Macaron instance.
|
||||
// It is the integration of most frequently used middlewares and helper methods.
|
||||
type Context struct {
|
||||
inject.Injector
|
||||
handlers []Handler
|
||||
action Handler
|
||||
index int
|
||||
|
||||
*Router
|
||||
Req Request
|
||||
Resp ResponseWriter
|
||||
params Params
|
||||
Render // Not nil only if you use macaran.Render middleware.
|
||||
Locale
|
||||
Data map[string]interface{}
|
||||
}
|
||||
|
||||
func (c *Context) handler() Handler {
|
||||
if c.index < len(c.handlers) {
|
||||
return c.handlers[c.index]
|
||||
}
|
||||
if c.index == len(c.handlers) {
|
||||
return c.action
|
||||
}
|
||||
panic("invalid index for context handler")
|
||||
}
|
||||
|
||||
func (c *Context) Next() {
|
||||
c.index += 1
|
||||
c.run()
|
||||
}
|
||||
|
||||
func (c *Context) Written() bool {
|
||||
return c.Resp.Written()
|
||||
}
|
||||
|
||||
func (c *Context) run() {
|
||||
for c.index <= len(c.handlers) {
|
||||
vals, err := c.Invoke(c.handler())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.index += 1
|
||||
|
||||
// if the handler returned something, write it to the http response
|
||||
if len(vals) > 0 {
|
||||
ev := c.GetVal(reflect.TypeOf(ReturnHandler(nil)))
|
||||
handleReturn := ev.Interface().(ReturnHandler)
|
||||
handleReturn(c, vals)
|
||||
}
|
||||
|
||||
if c.Written() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RemoteAddr returns more real IP address.
|
||||
func (ctx *Context) RemoteAddr() string {
|
||||
addr := ctx.Req.Header.Get("X-Real-IP")
|
||||
if len(addr) == 0 {
|
||||
addr = ctx.Req.Header.Get("X-Forwarded-For")
|
||||
if addr == "" {
|
||||
addr = ctx.Req.RemoteAddr
|
||||
if i := strings.LastIndex(addr, ":"); i > -1 {
|
||||
addr = addr[:i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func (ctx *Context) renderHTML(status int, setName, tplName string, data ...interface{}) {
|
||||
if ctx.Render == nil {
|
||||
panic("renderer middleware hasn't been registered")
|
||||
}
|
||||
if len(data) <= 0 {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, ctx.Data)
|
||||
} else if len(data) == 1 {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0])
|
||||
} else {
|
||||
ctx.Render.HTMLSet(status, setName, tplName, data[0], data[1].(HTMLOptions))
|
||||
}
|
||||
}
|
||||
|
||||
// HTML calls Render.HTML but allows less arguments.
|
||||
func (ctx *Context) HTML(status int, name string, data ...interface{}) {
|
||||
ctx.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data...)
|
||||
}
|
||||
|
||||
// HTML calls Render.HTMLSet but allows less arguments.
|
||||
func (ctx *Context) HTMLSet(status int, setName, tplName string, data ...interface{}) {
|
||||
ctx.renderHTML(status, setName, tplName, data...)
|
||||
}
|
||||
|
||||
func (ctx *Context) Redirect(location string, status ...int) {
|
||||
code := http.StatusFound
|
||||
if len(status) == 1 {
|
||||
code = status[0]
|
||||
}
|
||||
|
||||
http.Redirect(ctx.Resp, ctx.Req.Request, location, code)
|
||||
}
|
||||
|
||||
// Query querys form parameter.
|
||||
func (ctx *Context) Query(name string) string {
|
||||
if ctx.Req.Form == nil {
|
||||
ctx.Req.ParseForm()
|
||||
}
|
||||
return ctx.Req.Form.Get(name)
|
||||
}
|
||||
|
||||
// QueryTrim querys and trims spaces form parameter.
|
||||
func (ctx *Context) QueryTrim(name string) string {
|
||||
return strings.TrimSpace(ctx.Query(name))
|
||||
}
|
||||
|
||||
// QueryStrings returns a list of results by given query name.
|
||||
func (ctx *Context) QueryStrings(name string) []string {
|
||||
if ctx.Req.Form == nil {
|
||||
ctx.Req.ParseForm()
|
||||
}
|
||||
|
||||
vals, ok := ctx.Req.Form[name]
|
||||
if !ok {
|
||||
return []string{}
|
||||
}
|
||||
return vals
|
||||
}
|
||||
|
||||
// QueryEscape returns escapred query result.
|
||||
func (ctx *Context) QueryEscape(name string) string {
|
||||
return template.HTMLEscapeString(ctx.Query(name))
|
||||
}
|
||||
|
||||
// QueryInt returns query result in int type.
|
||||
func (ctx *Context) QueryInt(name string) int {
|
||||
return com.StrTo(ctx.Query(name)).MustInt()
|
||||
}
|
||||
|
||||
// QueryInt64 returns query result in int64 type.
|
||||
func (ctx *Context) QueryInt64(name string) int64 {
|
||||
return com.StrTo(ctx.Query(name)).MustInt64()
|
||||
}
|
||||
|
||||
// QueryFloat64 returns query result in float64 type.
|
||||
func (ctx *Context) QueryFloat64(name string) float64 {
|
||||
v, _ := strconv.ParseFloat(ctx.Query(name), 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// Params returns value of given param name.
|
||||
// e.g. ctx.Params(":uid") or ctx.Params("uid")
|
||||
func (ctx *Context) Params(name string) string {
|
||||
if len(name) == 0 {
|
||||
return ""
|
||||
}
|
||||
if name[0] != '*' && name[0] != ':' {
|
||||
name = ":" + name
|
||||
}
|
||||
return ctx.params[name]
|
||||
}
|
||||
|
||||
// SetParams sets value of param with given name.
|
||||
func (ctx *Context) SetParams(name, val string) {
|
||||
if !strings.HasPrefix(name, ":") {
|
||||
name = ":" + name
|
||||
}
|
||||
ctx.params[name] = val
|
||||
}
|
||||
|
||||
// ParamsEscape returns escapred params result.
|
||||
// e.g. ctx.ParamsEscape(":uname")
|
||||
func (ctx *Context) ParamsEscape(name string) string {
|
||||
return template.HTMLEscapeString(ctx.Params(name))
|
||||
}
|
||||
|
||||
// ParamsInt returns params result in int type.
|
||||
// e.g. ctx.ParamsInt(":uid")
|
||||
func (ctx *Context) ParamsInt(name string) int {
|
||||
return com.StrTo(ctx.Params(name)).MustInt()
|
||||
}
|
||||
|
||||
// ParamsInt64 returns params result in int64 type.
|
||||
// e.g. ctx.ParamsInt64(":uid")
|
||||
func (ctx *Context) ParamsInt64(name string) int64 {
|
||||
return com.StrTo(ctx.Params(name)).MustInt64()
|
||||
}
|
||||
|
||||
// ParamsFloat64 returns params result in int64 type.
|
||||
// e.g. ctx.ParamsFloat64(":uid")
|
||||
func (ctx *Context) ParamsFloat64(name string) float64 {
|
||||
v, _ := strconv.ParseFloat(ctx.Params(name), 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// GetFile returns information about user upload file by given form field name.
|
||||
func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
|
||||
return ctx.Req.FormFile(name)
|
||||
}
|
||||
|
||||
// SetCookie sets given cookie value to response header.
|
||||
// FIXME: IE support? http://golanghome.com/post/620#reply2
|
||||
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
|
||||
cookie := http.Cookie{}
|
||||
cookie.Name = name
|
||||
cookie.Value = url.QueryEscape(value)
|
||||
|
||||
if len(others) > 0 {
|
||||
switch v := others[0].(type) {
|
||||
case int:
|
||||
cookie.MaxAge = v
|
||||
case int64:
|
||||
cookie.MaxAge = int(v)
|
||||
case int32:
|
||||
cookie.MaxAge = int(v)
|
||||
}
|
||||
}
|
||||
|
||||
cookie.Path = "/"
|
||||
if len(others) > 1 {
|
||||
if v, ok := others[1].(string); ok && len(v) > 0 {
|
||||
cookie.Path = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(others) > 2 {
|
||||
if v, ok := others[2].(string); ok && len(v) > 0 {
|
||||
cookie.Domain = v
|
||||
}
|
||||
}
|
||||
|
||||
if len(others) > 3 {
|
||||
switch v := others[3].(type) {
|
||||
case bool:
|
||||
cookie.Secure = v
|
||||
default:
|
||||
if others[3] != nil {
|
||||
cookie.Secure = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(others) > 4 {
|
||||
if v, ok := others[4].(bool); ok && v {
|
||||
cookie.HttpOnly = true
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Resp.Header().Add("Set-Cookie", cookie.String())
|
||||
}
|
||||
|
||||
// GetCookie returns given cookie value from request header.
|
||||
func (ctx *Context) GetCookie(name string) string {
|
||||
cookie, err := ctx.Req.Cookie(name)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
val, _ := url.QueryUnescape(cookie.Value)
|
||||
return val
|
||||
}
|
||||
|
||||
// GetCookieInt returns cookie result in int type.
|
||||
func (ctx *Context) GetCookieInt(name string) int {
|
||||
return com.StrTo(ctx.GetCookie(name)).MustInt()
|
||||
}
|
||||
|
||||
// GetCookieInt64 returns cookie result in int64 type.
|
||||
func (ctx *Context) GetCookieInt64(name string) int64 {
|
||||
return com.StrTo(ctx.GetCookie(name)).MustInt64()
|
||||
}
|
||||
|
||||
// GetCookieFloat64 returns cookie result in float64 type.
|
||||
func (ctx *Context) GetCookieFloat64(name string) float64 {
|
||||
v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
|
||||
return v
|
||||
}
|
||||
|
||||
var defaultCookieSecret string
|
||||
|
||||
// SetDefaultCookieSecret sets global default secure cookie secret.
|
||||
func (m *Macaron) SetDefaultCookieSecret(secret string) {
|
||||
defaultCookieSecret = secret
|
||||
}
|
||||
|
||||
// SetSecureCookie sets given cookie value to response header with default secret string.
|
||||
func (ctx *Context) SetSecureCookie(name, value string, others ...interface{}) {
|
||||
ctx.SetSuperSecureCookie(defaultCookieSecret, name, value, others...)
|
||||
}
|
||||
|
||||
// GetSecureCookie returns given cookie value from request header with default secret string.
|
||||
func (ctx *Context) GetSecureCookie(key string) (string, bool) {
|
||||
return ctx.GetSuperSecureCookie(defaultCookieSecret, key)
|
||||
}
|
||||
|
||||
// SetSuperSecureCookie sets given cookie value to response header with secret string.
|
||||
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, others ...interface{}) {
|
||||
m := md5.Sum([]byte(secret))
|
||||
secret = hex.EncodeToString(m[:])
|
||||
text, err := com.AESEncrypt([]byte(secret), []byte(value))
|
||||
if err != nil {
|
||||
panic("error encrypting cookie: " + err.Error())
|
||||
}
|
||||
ctx.SetCookie(name, hex.EncodeToString(text), others...)
|
||||
}
|
||||
|
||||
// GetSuperSecureCookie returns given cookie value from request header with secret string.
|
||||
func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) {
|
||||
val := ctx.GetCookie(key)
|
||||
if val == "" {
|
||||
return "", false
|
||||
}
|
||||
|
||||
data, err := hex.DecodeString(val)
|
||||
if err != nil {
|
||||
return "", false
|
||||
}
|
||||
|
||||
m := md5.Sum([]byte(secret))
|
||||
secret = hex.EncodeToString(m[:])
|
||||
text, err := com.AESDecrypt([]byte(secret), data)
|
||||
return string(text), err == nil
|
||||
}
|
||||
|
||||
func (ctx *Context) setRawContentHeader() {
|
||||
ctx.Resp.Header().Set("Content-Description", "Raw content")
|
||||
ctx.Resp.Header().Set("Content-Type", "text/plain")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
}
|
||||
|
||||
// ServeContent serves given content to response.
|
||||
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
|
||||
modtime := time.Now()
|
||||
for _, p := range params {
|
||||
switch v := p.(type) {
|
||||
case time.Time:
|
||||
modtime = v
|
||||
}
|
||||
}
|
||||
|
||||
ctx.setRawContentHeader()
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
|
||||
}
|
||||
|
||||
// ServeFileContent serves given file as content to response.
|
||||
func (ctx *Context) ServeFileContent(file string, names ...string) {
|
||||
var name string
|
||||
if len(names) > 0 {
|
||||
name = names[0]
|
||||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
if Env == PROD {
|
||||
http.Error(ctx.Resp, "Internal Server Error", 500)
|
||||
} else {
|
||||
http.Error(ctx.Resp, err.Error(), 500)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
ctx.setRawContentHeader()
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)
|
||||
}
|
||||
|
||||
// ServeFile serves given file to response.
|
||||
func (ctx *Context) ServeFile(file string, names ...string) {
|
||||
var name string
|
||||
if len(names) > 0 {
|
||||
name = names[0]
|
||||
} else {
|
||||
name = path.Base(file)
|
||||
}
|
||||
ctx.Resp.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Resp.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
|
||||
ctx.Resp.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Resp.Header().Set("Expires", "0")
|
||||
ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Resp.Header().Set("Pragma", "public")
|
||||
http.ServeFile(ctx.Resp, ctx.Req.Request, file)
|
||||
}
|
||||
|
||||
// ChangeStaticPath changes static path from old to new one.
|
||||
func (ctx *Context) ChangeStaticPath(oldPath, newPath string) {
|
||||
if !filepath.IsAbs(oldPath) {
|
||||
oldPath = filepath.Join(Root, oldPath)
|
||||
}
|
||||
dir := statics.Get(oldPath)
|
||||
if dir != nil {
|
||||
statics.Delete(oldPath)
|
||||
|
||||
if !filepath.IsAbs(newPath) {
|
||||
newPath = filepath.Join(Root, newPath)
|
||||
}
|
||||
*dir = http.Dir(newPath)
|
||||
statics.Set(dir)
|
||||
}
|
||||
}
|
||||
370
Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go
generated
vendored
Normal file
370
Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go
generated
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Context(t *testing.T) {
|
||||
Convey("Do advanced encapsulation operations", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderers(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}, "fixtures/basic2"))
|
||||
|
||||
Convey("Get request body", func() {
|
||||
m.Get("/body1", func(ctx *Context) {
|
||||
data, err := ioutil.ReadAll(ctx.Req.Body().ReadCloser())
|
||||
So(err, ShouldBeNil)
|
||||
So(string(data), ShouldEqual, "This is my request body")
|
||||
})
|
||||
m.Get("/body2", func(ctx *Context) {
|
||||
data, err := ctx.Req.Body().Bytes()
|
||||
So(err, ShouldBeNil)
|
||||
So(string(data), ShouldEqual, "This is my request body")
|
||||
})
|
||||
m.Get("/body3", func(ctx *Context) {
|
||||
data, err := ctx.Req.Body().String()
|
||||
So(err, ShouldBeNil)
|
||||
So(data, ShouldEqual, "This is my request body")
|
||||
})
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/body"+com.ToStr(i), nil)
|
||||
req.Body = ioutil.NopCloser(bytes.NewBufferString("This is my request body"))
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
}
|
||||
})
|
||||
|
||||
Convey("Get remote IP address", func() {
|
||||
m.Get("/remoteaddr", func(ctx *Context) string {
|
||||
return ctx.RemoteAddr()
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/remoteaddr", nil)
|
||||
req.RemoteAddr = "127.0.0.1:3333"
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "127.0.0.1")
|
||||
})
|
||||
|
||||
Convey("Render HTML", func() {
|
||||
|
||||
Convey("Normal HTML", func() {
|
||||
m.Get("/html", func(ctx *Context) {
|
||||
ctx.HTML(304, "hello", "Unknwon") // 304 for logger test.
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/html", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
|
||||
})
|
||||
|
||||
Convey("HTML template set", func() {
|
||||
m.Get("/html2", func(ctx *Context) {
|
||||
ctx.Data["Name"] = "Unknwon"
|
||||
ctx.HTMLSet(200, "basic2", "hello2")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/html2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
|
||||
})
|
||||
|
||||
Convey("With layout", func() {
|
||||
m.Get("/layout", func(ctx *Context) {
|
||||
ctx.HTML(200, "hello", "Unknwon", HTMLOptions{"layout"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/layout", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "head<h1>Hello Unknwon</h1>foot")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Parse from and query", func() {
|
||||
m.Get("/query", func(ctx *Context) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(ctx.QueryTrim("name") + " ")
|
||||
buf.WriteString(ctx.QueryEscape("name") + " ")
|
||||
buf.WriteString(com.ToStr(ctx.QueryInt("int")) + " ")
|
||||
buf.WriteString(com.ToStr(ctx.QueryInt64("int64")) + " ")
|
||||
buf.WriteString(com.ToStr(ctx.QueryFloat64("float64")) + " ")
|
||||
return buf.String()
|
||||
})
|
||||
m.Get("/query2", func(ctx *Context) string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(strings.Join(ctx.QueryStrings("list"), ",") + " ")
|
||||
buf.WriteString(strings.Join(ctx.QueryStrings("404"), ",") + " ")
|
||||
return buf.String()
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/query?name=Unknwon&int=12&int64=123&float64=1.25", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon 12 123 1.25 ")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/query2?list=item1&list=item2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "item1,item2 ")
|
||||
})
|
||||
|
||||
Convey("URL parameter", func() {
|
||||
m.Get("/:name/:int/:int64/:float64", func(ctx *Context) string {
|
||||
var buf bytes.Buffer
|
||||
ctx.SetParams("name", ctx.Params("name"))
|
||||
buf.WriteString(ctx.Params(""))
|
||||
buf.WriteString(ctx.Params(":name") + " ")
|
||||
buf.WriteString(ctx.ParamsEscape(":name") + " ")
|
||||
buf.WriteString(com.ToStr(ctx.ParamsInt(":int")) + " ")
|
||||
buf.WriteString(com.ToStr(ctx.ParamsInt64(":int64")) + " ")
|
||||
buf.WriteString(com.ToStr(ctx.ParamsFloat64(":float64")) + " ")
|
||||
return buf.String()
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/user/1/13/1.24", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "user user 1 13 1.24 ")
|
||||
})
|
||||
|
||||
Convey("Get file", func() {
|
||||
m.Get("/getfile", func(ctx *Context) {
|
||||
ctx.GetFile("hi")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/getfile", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Set and get cookie", func() {
|
||||
m.Get("/set", func(ctx *Context) {
|
||||
ctx.SetCookie("user", "Unknwon", 1, "/", "localhost", true, true)
|
||||
ctx.SetCookie("user", "Unknwon", int32(1), "/", "localhost", 1)
|
||||
ctx.SetCookie("user", "Unknwon", int64(1))
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/set", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
|
||||
|
||||
m.Get("/get", func(ctx *Context) string {
|
||||
ctx.GetCookie("404")
|
||||
So(ctx.GetCookieInt("uid"), ShouldEqual, 1)
|
||||
So(ctx.GetCookieInt64("uid"), ShouldEqual, 1)
|
||||
So(ctx.GetCookieFloat64("balance"), ShouldEqual, 1.25)
|
||||
return ctx.GetCookie("user")
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", "user=Unknwon; uid=1; balance=1.25")
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Unknwon")
|
||||
})
|
||||
|
||||
Convey("Set and get secure cookie", func() {
|
||||
m.SetDefaultCookieSecret("macaron")
|
||||
m.Get("/set", func(ctx *Context) {
|
||||
ctx.SetSecureCookie("user", "Unknwon", 1)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/set", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
cookie := resp.Header().Get("Set-Cookie")
|
||||
|
||||
m.Get("/get", func(ctx *Context) string {
|
||||
name, ok := ctx.GetSecureCookie("user")
|
||||
So(ok, ShouldBeTrue)
|
||||
return name
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
req.Header.Set("Cookie", cookie)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Unknwon")
|
||||
})
|
||||
|
||||
Convey("Serve files", func() {
|
||||
m.Get("/file", func(ctx *Context) {
|
||||
ctx.ServeFile("fixtures/custom_funcs/index.tmpl")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/file", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
|
||||
|
||||
m.Get("/file2", func(ctx *Context) {
|
||||
ctx.ServeFile("fixtures/custom_funcs/index.tmpl", "ok.tmpl")
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/file2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
|
||||
})
|
||||
|
||||
Convey("Serve file content", func() {
|
||||
m.Get("/file", func(ctx *Context) {
|
||||
ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/file", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
|
||||
|
||||
m.Get("/file2", func(ctx *Context) {
|
||||
ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl", "ok.tmpl")
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/file2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
|
||||
|
||||
m.Get("/file3", func(ctx *Context) {
|
||||
ctx.ServeFileContent("404.tmpl")
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/file3", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "open 404.tmpl: no such file or directory\n")
|
||||
So(resp.Code, ShouldEqual, 500)
|
||||
})
|
||||
|
||||
Convey("Serve content", func() {
|
||||
m.Get("/content", func(ctx *Context) {
|
||||
ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")))
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/content", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Hello world!")
|
||||
|
||||
m.Get("/content2", func(ctx *Context) {
|
||||
ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")), time.Now())
|
||||
})
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/content2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Hello world!")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Context_Render(t *testing.T) {
|
||||
Convey("Invalid render", t, func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
|
||||
m := New()
|
||||
m.Get("/", func(ctx *Context) {
|
||||
ctx.HTML(200, "hey")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Context_Redirect(t *testing.T) {
|
||||
Convey("Context with default redirect", t, func() {
|
||||
url, err := url.Parse("http://localhost/path/one")
|
||||
So(err, ShouldBeNil)
|
||||
resp := httptest.NewRecorder()
|
||||
req := http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
}
|
||||
ctx := &Context{
|
||||
Req: Request{&req},
|
||||
Resp: NewResponseWriter(resp),
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
ctx.Redirect("two")
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusFound)
|
||||
So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two")
|
||||
})
|
||||
|
||||
Convey("Context with custom redirect", t, func() {
|
||||
url, err := url.Parse("http://localhost/path/one")
|
||||
So(err, ShouldBeNil)
|
||||
resp := httptest.NewRecorder()
|
||||
req := http.Request{
|
||||
Method: "GET",
|
||||
URL: url,
|
||||
}
|
||||
ctx := &Context{
|
||||
Req: Request{&req},
|
||||
Resp: NewResponseWriter(resp),
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
ctx.Redirect("two", 307)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusTemporaryRedirect)
|
||||
So(resp.HeaderMap["Location"][0], ShouldEqual, "/path/two")
|
||||
})
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/admin/index.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/admin/index.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Admin {{.}}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/another_layout.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/another_layout.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
another head{{ yield }}another foot
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/content.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/content.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>{{ . }}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/current_layout.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/current_layout.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{{ current }} head{{ yield }}{{ current }} foot
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/delims.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/delims.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Hello {[{.}]}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/hello.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/hello.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Hello {{.}}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/hypertext.html
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/hypertext.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hypertext!
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/layout.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic/layout.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
head{{ yield }}foot
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic2/hello.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic2/hello.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>What's up, {{.}}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic2/hello2.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/basic2/hello2.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<h1>Hello {{.Name}}</h1>
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/custom_funcs/index.tmpl
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/fixtures/custom_funcs/index.tmpl
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{{ myCustomFunc }}
|
||||
81
Godeps/_workspace/src/github.com/Unknwon/macaron/gzip.go
generated
vendored
Normal file
81
Godeps/_workspace/src/github.com/Unknwon/macaron/gzip.go
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderAcceptEncoding = "Accept-Encoding"
|
||||
HeaderContentEncoding = "Content-Encoding"
|
||||
HeaderContentLength = "Content-Length"
|
||||
HeaderContentType = "Content-Type"
|
||||
HeaderVary = "Vary"
|
||||
)
|
||||
|
||||
// Gziper returns a Handler that adds gzip compression to all requests.
|
||||
// Make sure to include the Gzip middleware above other middleware
|
||||
// that alter the response body (like the render middleware).
|
||||
func Gziper() Handler {
|
||||
return func(ctx *Context) {
|
||||
if !strings.Contains(ctx.Req.Header.Get(HeaderAcceptEncoding), "gzip") {
|
||||
return
|
||||
}
|
||||
|
||||
headers := ctx.Resp.Header()
|
||||
headers.Set(HeaderContentEncoding, "gzip")
|
||||
headers.Set(HeaderVary, HeaderAcceptEncoding)
|
||||
|
||||
gz := gzip.NewWriter(ctx.Resp)
|
||||
defer gz.Close()
|
||||
|
||||
gzw := gzipResponseWriter{gz, ctx.Resp}
|
||||
ctx.Resp = gzw
|
||||
ctx.MapTo(gzw, (*http.ResponseWriter)(nil))
|
||||
|
||||
ctx.Next()
|
||||
|
||||
// delete content length after we know we have been written to
|
||||
gzw.Header().Del("Content-Length")
|
||||
}
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
w *gzip.Writer
|
||||
ResponseWriter
|
||||
}
|
||||
|
||||
func (grw gzipResponseWriter) Write(p []byte) (int, error) {
|
||||
if len(grw.Header().Get(HeaderContentType)) == 0 {
|
||||
grw.Header().Set(HeaderContentType, http.DetectContentType(p))
|
||||
}
|
||||
|
||||
return grw.w.Write(p)
|
||||
}
|
||||
|
||||
func (grw gzipResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
hijacker, ok := grw.ResponseWriter.(http.Hijacker)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("the ResponseWriter doesn't support the Hijacker interface")
|
||||
}
|
||||
return hijacker.Hijack()
|
||||
}
|
||||
65
Godeps/_workspace/src/github.com/Unknwon/macaron/gzip_test.go
generated
vendored
Normal file
65
Godeps/_workspace/src/github.com/Unknwon/macaron/gzip_test.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Gzip(t *testing.T) {
|
||||
Convey("Gzip response content", t, func() {
|
||||
before := false
|
||||
|
||||
m := New()
|
||||
m.Use(Gziper())
|
||||
m.Use(func(r http.ResponseWriter) {
|
||||
r.(ResponseWriter).Before(func(rw ResponseWriter) {
|
||||
before = true
|
||||
})
|
||||
})
|
||||
m.Get("/", func() string { return "hello wolrd!" })
|
||||
|
||||
// Not yet gzip.
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
_, ok := resp.HeaderMap[HeaderContentEncoding]
|
||||
So(ok, ShouldBeFalse)
|
||||
|
||||
ce := resp.Header().Get(HeaderContentEncoding)
|
||||
So(strings.EqualFold(ce, "gzip"), ShouldBeFalse)
|
||||
|
||||
// Gzip now.
|
||||
resp = httptest.NewRecorder()
|
||||
req.Header.Set(HeaderAcceptEncoding, "gzip")
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
_, ok = resp.HeaderMap[HeaderContentEncoding]
|
||||
So(ok, ShouldBeTrue)
|
||||
|
||||
ce = resp.Header().Get(HeaderContentEncoding)
|
||||
So(strings.EqualFold(ce, "gzip"), ShouldBeTrue)
|
||||
|
||||
So(before, ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
4
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/README.md
generated
vendored
Normal file
4
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/README.md
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
inject
|
||||
======
|
||||
|
||||
Dependency injection for go
|
||||
187
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject.go
generated
vendored
Normal file
187
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject.go
generated
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
// Package inject provides utilities for mapping and injecting dependencies in various ways.
|
||||
package inject
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// Injector represents an interface for mapping and injecting dependencies into structs
|
||||
// and function arguments.
|
||||
type Injector interface {
|
||||
Applicator
|
||||
Invoker
|
||||
TypeMapper
|
||||
// SetParent sets the parent of the injector. If the injector cannot find a
|
||||
// dependency in its Type map it will check its parent before returning an
|
||||
// error.
|
||||
SetParent(Injector)
|
||||
}
|
||||
|
||||
// Applicator represents an interface for mapping dependencies to a struct.
|
||||
type Applicator interface {
|
||||
// Maps dependencies in the Type map to each field in the struct
|
||||
// that is tagged with 'inject'. Returns an error if the injection
|
||||
// fails.
|
||||
Apply(interface{}) error
|
||||
}
|
||||
|
||||
// Invoker represents an interface for calling functions via reflection.
|
||||
type Invoker interface {
|
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type. Returns
|
||||
// a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
Invoke(interface{}) ([]reflect.Value, error)
|
||||
}
|
||||
|
||||
// TypeMapper represents an interface for mapping interface{} values based on type.
|
||||
type TypeMapper interface {
|
||||
// Maps the interface{} value based on its immediate type from reflect.TypeOf.
|
||||
Map(interface{}) TypeMapper
|
||||
// Maps the interface{} value based on the pointer of an Interface provided.
|
||||
// This is really only useful for mapping a value as an interface, as interfaces
|
||||
// cannot at this time be referenced directly without a pointer.
|
||||
MapTo(interface{}, interface{}) TypeMapper
|
||||
// Provides a possibility to directly insert a mapping based on type and value.
|
||||
// This makes it possible to directly map type arguments not possible to instantiate
|
||||
// with reflect like unidirectional channels.
|
||||
Set(reflect.Type, reflect.Value) TypeMapper
|
||||
// Returns the Value that is mapped to the current type. Returns a zeroed Value if
|
||||
// the Type has not been mapped.
|
||||
GetVal(reflect.Type) reflect.Value
|
||||
}
|
||||
|
||||
type injector struct {
|
||||
values map[reflect.Type]reflect.Value
|
||||
parent Injector
|
||||
}
|
||||
|
||||
// InterfaceOf dereferences a pointer to an Interface type.
|
||||
// It panics if value is not an pointer to an interface.
|
||||
func InterfaceOf(value interface{}) reflect.Type {
|
||||
t := reflect.TypeOf(value)
|
||||
|
||||
for t.Kind() == reflect.Ptr {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
if t.Kind() != reflect.Interface {
|
||||
panic("Called inject.InterfaceOf with a value that is not a pointer to an interface. (*MyInterface)(nil)")
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// New returns a new Injector.
|
||||
func New() Injector {
|
||||
return &injector{
|
||||
values: make(map[reflect.Type]reflect.Value),
|
||||
}
|
||||
}
|
||||
|
||||
// Invoke attempts to call the interface{} provided as a function,
|
||||
// providing dependencies for function arguments based on Type.
|
||||
// Returns a slice of reflect.Value representing the returned values of the function.
|
||||
// Returns an error if the injection fails.
|
||||
// It panics if f is not a function
|
||||
func (inj *injector) Invoke(f interface{}) ([]reflect.Value, error) {
|
||||
t := reflect.TypeOf(f)
|
||||
|
||||
var in = make([]reflect.Value, t.NumIn()) //Panic if t is not kind of Func
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
argType := t.In(i)
|
||||
val := inj.GetVal(argType)
|
||||
if !val.IsValid() {
|
||||
return nil, fmt.Errorf("Value not found for type %v", argType)
|
||||
}
|
||||
|
||||
in[i] = val
|
||||
}
|
||||
|
||||
return reflect.ValueOf(f).Call(in), nil
|
||||
}
|
||||
|
||||
// Maps dependencies in the Type map to each field in the struct
|
||||
// that is tagged with 'inject'.
|
||||
// Returns an error if the injection fails.
|
||||
func (inj *injector) Apply(val interface{}) error {
|
||||
v := reflect.ValueOf(val)
|
||||
|
||||
for v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return nil // Should not panic here ?
|
||||
}
|
||||
|
||||
t := v.Type()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
f := v.Field(i)
|
||||
structField := t.Field(i)
|
||||
if f.CanSet() && (structField.Tag == "inject" || structField.Tag.Get("inject") != "") {
|
||||
ft := f.Type()
|
||||
v := inj.GetVal(ft)
|
||||
if !v.IsValid() {
|
||||
return fmt.Errorf("Value not found for type %v", ft)
|
||||
}
|
||||
|
||||
f.Set(v)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Maps the concrete value of val to its dynamic type using reflect.TypeOf,
|
||||
// It returns the TypeMapper registered in.
|
||||
func (i *injector) Map(val interface{}) TypeMapper {
|
||||
i.values[reflect.TypeOf(val)] = reflect.ValueOf(val)
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *injector) MapTo(val interface{}, ifacePtr interface{}) TypeMapper {
|
||||
i.values[InterfaceOf(ifacePtr)] = reflect.ValueOf(val)
|
||||
return i
|
||||
}
|
||||
|
||||
// Maps the given reflect.Type to the given reflect.Value and returns
|
||||
// the Typemapper the mapping has been registered in.
|
||||
func (i *injector) Set(typ reflect.Type, val reflect.Value) TypeMapper {
|
||||
i.values[typ] = val
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *injector) GetVal(t reflect.Type) reflect.Value {
|
||||
val := i.values[t]
|
||||
|
||||
if val.IsValid() {
|
||||
return val
|
||||
}
|
||||
|
||||
// no concrete types found, try to find implementors
|
||||
// if t is an interface
|
||||
if t.Kind() == reflect.Interface {
|
||||
for k, v := range i.values {
|
||||
if k.Implements(t) {
|
||||
val = v
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Still no type found, try to look it up on the parent
|
||||
if !val.IsValid() && i.parent != nil {
|
||||
val = i.parent.GetVal(t)
|
||||
}
|
||||
|
||||
return val
|
||||
|
||||
}
|
||||
|
||||
func (i *injector) SetParent(parent Injector) {
|
||||
i.parent = parent
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject.goconvey
generated
vendored
Normal file
1
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject.goconvey
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
ignore
|
||||
174
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go
generated
vendored
Normal file
174
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 inject_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/macaron/inject"
|
||||
)
|
||||
|
||||
type SpecialString interface {
|
||||
}
|
||||
|
||||
type TestStruct struct {
|
||||
Dep1 string `inject:"t" json:"-"`
|
||||
Dep2 SpecialString `inject`
|
||||
Dep3 string
|
||||
}
|
||||
|
||||
type Greeter struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (g *Greeter) String() string {
|
||||
return "Hello, My name is" + g.Name
|
||||
}
|
||||
|
||||
/* Test Helpers */
|
||||
func expect(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
||||
func refute(t *testing.T, a interface{}, b interface{}) {
|
||||
if a == b {
|
||||
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_InjectorInvoke(t *testing.T) {
|
||||
injector := inject.New()
|
||||
expect(t, injector == nil, false)
|
||||
|
||||
dep := "some dependency"
|
||||
injector.Map(dep)
|
||||
dep2 := "another dep"
|
||||
injector.MapTo(dep2, (*SpecialString)(nil))
|
||||
dep3 := make(chan *SpecialString)
|
||||
dep4 := make(chan *SpecialString)
|
||||
typRecv := reflect.ChanOf(reflect.RecvDir, reflect.TypeOf(dep3).Elem())
|
||||
typSend := reflect.ChanOf(reflect.SendDir, reflect.TypeOf(dep4).Elem())
|
||||
injector.Set(typRecv, reflect.ValueOf(dep3))
|
||||
injector.Set(typSend, reflect.ValueOf(dep4))
|
||||
|
||||
_, err := injector.Invoke(func(d1 string, d2 SpecialString, d3 <-chan *SpecialString, d4 chan<- *SpecialString) {
|
||||
expect(t, d1, dep)
|
||||
expect(t, d2, dep2)
|
||||
expect(t, reflect.TypeOf(d3).Elem(), reflect.TypeOf(dep3).Elem())
|
||||
expect(t, reflect.TypeOf(d4).Elem(), reflect.TypeOf(dep4).Elem())
|
||||
expect(t, reflect.TypeOf(d3).ChanDir(), reflect.RecvDir)
|
||||
expect(t, reflect.TypeOf(d4).ChanDir(), reflect.SendDir)
|
||||
})
|
||||
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func Test_InjectorInvokeReturnValues(t *testing.T) {
|
||||
injector := inject.New()
|
||||
expect(t, injector == nil, false)
|
||||
|
||||
dep := "some dependency"
|
||||
injector.Map(dep)
|
||||
dep2 := "another dep"
|
||||
injector.MapTo(dep2, (*SpecialString)(nil))
|
||||
|
||||
result, err := injector.Invoke(func(d1 string, d2 SpecialString) string {
|
||||
expect(t, d1, dep)
|
||||
expect(t, d2, dep2)
|
||||
return "Hello world"
|
||||
})
|
||||
|
||||
expect(t, result[0].String(), "Hello world")
|
||||
expect(t, err, nil)
|
||||
}
|
||||
|
||||
func Test_InjectorApply(t *testing.T) {
|
||||
injector := inject.New()
|
||||
|
||||
injector.Map("a dep").MapTo("another dep", (*SpecialString)(nil))
|
||||
|
||||
s := TestStruct{}
|
||||
err := injector.Apply(&s)
|
||||
expect(t, err, nil)
|
||||
|
||||
expect(t, s.Dep1, "a dep")
|
||||
expect(t, s.Dep2, "another dep")
|
||||
}
|
||||
|
||||
func Test_InterfaceOf(t *testing.T) {
|
||||
iType := inject.InterfaceOf((*SpecialString)(nil))
|
||||
expect(t, iType.Kind(), reflect.Interface)
|
||||
|
||||
iType = inject.InterfaceOf((**SpecialString)(nil))
|
||||
expect(t, iType.Kind(), reflect.Interface)
|
||||
|
||||
// Expecting nil
|
||||
defer func() {
|
||||
rec := recover()
|
||||
refute(t, rec, nil)
|
||||
}()
|
||||
iType = inject.InterfaceOf((*testing.T)(nil))
|
||||
}
|
||||
|
||||
func Test_InjectorSet(t *testing.T) {
|
||||
injector := inject.New()
|
||||
typ := reflect.TypeOf("string")
|
||||
typSend := reflect.ChanOf(reflect.SendDir, typ)
|
||||
typRecv := reflect.ChanOf(reflect.RecvDir, typ)
|
||||
|
||||
// instantiating unidirectional channels is not possible using reflect
|
||||
// http://golang.org/src/pkg/reflect/value.go?s=60463:60504#L2064
|
||||
chanRecv := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
|
||||
chanSend := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, typ), 0)
|
||||
|
||||
injector.Set(typSend, chanSend)
|
||||
injector.Set(typRecv, chanRecv)
|
||||
|
||||
expect(t, injector.GetVal(typSend).IsValid(), true)
|
||||
expect(t, injector.GetVal(typRecv).IsValid(), true)
|
||||
expect(t, injector.GetVal(chanSend.Type()).IsValid(), false)
|
||||
}
|
||||
|
||||
func Test_InjectorGet(t *testing.T) {
|
||||
injector := inject.New()
|
||||
|
||||
injector.Map("some dependency")
|
||||
|
||||
expect(t, injector.GetVal(reflect.TypeOf("string")).IsValid(), true)
|
||||
expect(t, injector.GetVal(reflect.TypeOf(11)).IsValid(), false)
|
||||
}
|
||||
|
||||
func Test_InjectorSetParent(t *testing.T) {
|
||||
injector := inject.New()
|
||||
injector.MapTo("another dep", (*SpecialString)(nil))
|
||||
|
||||
injector2 := inject.New()
|
||||
injector2.SetParent(injector)
|
||||
|
||||
expect(t, injector2.GetVal(inject.InterfaceOf((*SpecialString)(nil))).IsValid(), true)
|
||||
}
|
||||
|
||||
func TestInjectImplementors(t *testing.T) {
|
||||
injector := inject.New()
|
||||
g := &Greeter{"Jeremy"}
|
||||
injector.Map(g)
|
||||
|
||||
expect(t, injector.GetVal(inject.InterfaceOf((*fmt.Stringer)(nil))).IsValid(), true)
|
||||
}
|
||||
61
Godeps/_workspace/src/github.com/Unknwon/macaron/logger.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/Unknwon/macaron/logger.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ColorLog = true
|
||||
|
||||
func init() {
|
||||
ColorLog = runtime.GOOS != "windows"
|
||||
}
|
||||
|
||||
// Logger returns a middleware handler that logs the request as it goes in and the response as it goes out.
|
||||
func Logger() Handler {
|
||||
return func(ctx *Context, log *log.Logger) {
|
||||
start := time.Now()
|
||||
|
||||
log.Printf("Started %s %s for %s", ctx.Req.Method, ctx.Req.RequestURI, ctx.RemoteAddr())
|
||||
|
||||
rw := ctx.Resp.(ResponseWriter)
|
||||
ctx.Next()
|
||||
|
||||
content := fmt.Sprintf("Completed %s %v %s in %v", ctx.Req.RequestURI, rw.Status(), http.StatusText(rw.Status()), time.Since(start))
|
||||
if ColorLog {
|
||||
switch rw.Status() {
|
||||
case 200, 201, 202:
|
||||
content = fmt.Sprintf("\033[1;32m%s\033[0m", content)
|
||||
case 301, 302:
|
||||
content = fmt.Sprintf("\033[1;37m%s\033[0m", content)
|
||||
case 304:
|
||||
content = fmt.Sprintf("\033[1;33m%s\033[0m", content)
|
||||
case 401, 403:
|
||||
content = fmt.Sprintf("\033[4;31m%s\033[0m", content)
|
||||
case 404:
|
||||
content = fmt.Sprintf("\033[1;31m%s\033[0m", content)
|
||||
case 500:
|
||||
content = fmt.Sprintf("\033[1;36m%s\033[0m", content)
|
||||
}
|
||||
}
|
||||
log.Println(content)
|
||||
}
|
||||
}
|
||||
67
Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go
generated
vendored
Normal file
67
Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Logger(t *testing.T) {
|
||||
Convey("Global logger", t, func() {
|
||||
buf := bytes.NewBufferString("")
|
||||
m := New()
|
||||
m.Map(log.New(buf, "[Macaron] ", 0))
|
||||
m.Use(Logger())
|
||||
m.Use(func(res http.ResponseWriter) {
|
||||
res.WriteHeader(http.StatusNotFound)
|
||||
})
|
||||
m.Get("/", func() {})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusNotFound)
|
||||
So(len(buf.String()), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
|
||||
if ColorLog {
|
||||
Convey("Color console output", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/:code:int", func(ctx *Context) (int, string) {
|
||||
return ctx.ParamsInt(":code"), ""
|
||||
})
|
||||
|
||||
// Just for testing if logger would capture.
|
||||
codes := []int{200, 201, 202, 301, 302, 304, 401, 403, 404, 500}
|
||||
for _, code := range codes {
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/"+com.ToStr(code), nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
273
Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go
generated
vendored
Normal file
273
Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron is a high productive and modular design web framework in Go.
|
||||
package macaron
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/Unknwon/macaron/inject"
|
||||
)
|
||||
|
||||
const _VERSION = "0.5.4.0318"
|
||||
|
||||
func Version() string {
|
||||
return _VERSION
|
||||
}
|
||||
|
||||
// Handler can be any callable function.
|
||||
// Macaron attempts to inject services into the handler's argument list,
|
||||
// and panics if an argument could not be fullfilled via dependency injection.
|
||||
type Handler interface{}
|
||||
|
||||
// validateHandler makes sure a handler is a callable function,
|
||||
// and panics if it is not.
|
||||
func validateHandler(h Handler) {
|
||||
if reflect.TypeOf(h).Kind() != reflect.Func {
|
||||
panic("Macaron handler must be a callable function")
|
||||
}
|
||||
}
|
||||
|
||||
// validateHandlers makes sure handlers are callable functions,
|
||||
// and panics if any of them is not.
|
||||
func validateHandlers(handlers []Handler) {
|
||||
for _, h := range handlers {
|
||||
validateHandler(h)
|
||||
}
|
||||
}
|
||||
|
||||
// Macaron represents the top level web application.
|
||||
// inject.Injector methods can be invoked to map services on a global level.
|
||||
type Macaron struct {
|
||||
inject.Injector
|
||||
befores []BeforeHandler
|
||||
handlers []Handler
|
||||
action Handler
|
||||
|
||||
urlPrefix string // For suburl support.
|
||||
*Router
|
||||
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewWithLogger creates a bare bones Macaron instance.
|
||||
// Use this method if you want to have full control over the middleware that is used.
|
||||
// You can specify logger output writer with this function.
|
||||
func NewWithLogger(out io.Writer) *Macaron {
|
||||
m := &Macaron{
|
||||
Injector: inject.New(),
|
||||
action: func() {},
|
||||
Router: NewRouter(),
|
||||
logger: log.New(out, "[Macaron] ", 0),
|
||||
}
|
||||
m.Router.m = m
|
||||
m.Map(m.logger)
|
||||
m.Map(defaultReturnHandler())
|
||||
m.notFound = func(resp http.ResponseWriter, req *http.Request) {
|
||||
c := m.createContext(resp, req)
|
||||
c.handlers = append(c.handlers, http.NotFound)
|
||||
c.run()
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// New creates a bare bones Macaron instance.
|
||||
// Use this method if you want to have full control over the middleware that is used.
|
||||
func New() *Macaron {
|
||||
return NewWithLogger(os.Stdout)
|
||||
}
|
||||
|
||||
// Classic creates a classic Macaron with some basic default middleware:
|
||||
// mocaron.Logger, mocaron.Recovery and mocaron.Static.
|
||||
func Classic() *Macaron {
|
||||
m := New()
|
||||
m.Use(Logger())
|
||||
m.Use(Recovery())
|
||||
m.Use(Static("public"))
|
||||
return m
|
||||
}
|
||||
|
||||
// Handlers sets the entire middleware stack with the given Handlers.
|
||||
// This will clear any current middleware handlers,
|
||||
// and panics if any of the handlers is not a callable function
|
||||
func (m *Macaron) Handlers(handlers ...Handler) {
|
||||
m.handlers = make([]Handler, 0)
|
||||
for _, handler := range handlers {
|
||||
m.Use(handler)
|
||||
}
|
||||
}
|
||||
|
||||
// Action sets the handler that will be called after all the middleware has been invoked.
|
||||
// This is set to macaron.Router in a macaron.Classic().
|
||||
func (m *Macaron) Action(handler Handler) {
|
||||
validateHandler(handler)
|
||||
m.action = handler
|
||||
}
|
||||
|
||||
// BeforeHandler represents a handler executes at beginning of every request.
|
||||
// Macaron stops future process when it returns true.
|
||||
type BeforeHandler func(rw http.ResponseWriter, req *http.Request) bool
|
||||
|
||||
func (m *Macaron) Before(handler BeforeHandler) {
|
||||
m.befores = append(m.befores, handler)
|
||||
}
|
||||
|
||||
// Use adds a middleware Handler to the stack,
|
||||
// and panics if the handler is not a callable func.
|
||||
// Middleware Handlers are invoked in the order that they are added.
|
||||
func (m *Macaron) Use(handler Handler) {
|
||||
validateHandler(handler)
|
||||
m.handlers = append(m.handlers, handler)
|
||||
}
|
||||
|
||||
func (m *Macaron) createContext(rw http.ResponseWriter, req *http.Request) *Context {
|
||||
c := &Context{
|
||||
Injector: inject.New(),
|
||||
handlers: m.handlers,
|
||||
action: m.action,
|
||||
index: 0,
|
||||
Router: m.Router,
|
||||
Req: Request{req},
|
||||
Resp: NewResponseWriter(rw),
|
||||
Data: make(map[string]interface{}),
|
||||
}
|
||||
c.SetParent(m)
|
||||
c.Map(c)
|
||||
c.MapTo(c.Resp, (*http.ResponseWriter)(nil))
|
||||
c.Map(req)
|
||||
return c
|
||||
}
|
||||
|
||||
// ServeHTTP is the HTTP Entry point for a Macaron instance.
|
||||
// Useful if you want to control your own HTTP server.
|
||||
// Be aware that none of middleware will run without registering any router.
|
||||
func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix)
|
||||
for _, h := range m.befores {
|
||||
if h(rw, req) {
|
||||
return
|
||||
}
|
||||
}
|
||||
m.Router.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func GetDefaultListenInfo() (string, int) {
|
||||
host := os.Getenv("HOST")
|
||||
if len(host) == 0 {
|
||||
host = "0.0.0.0"
|
||||
}
|
||||
port := com.StrTo(os.Getenv("PORT")).MustInt()
|
||||
if port == 0 {
|
||||
port = 4000
|
||||
}
|
||||
return host, port
|
||||
}
|
||||
|
||||
// Run the http server. Listening on os.GetEnv("PORT") or 4000 by default.
|
||||
func (m *Macaron) Run(args ...interface{}) {
|
||||
host, port := GetDefaultListenInfo()
|
||||
if len(args) == 1 {
|
||||
switch arg := args[0].(type) {
|
||||
case string:
|
||||
host = arg
|
||||
case int:
|
||||
port = arg
|
||||
}
|
||||
} else if len(args) >= 2 {
|
||||
if arg, ok := args[0].(string); ok {
|
||||
host = arg
|
||||
}
|
||||
if arg, ok := args[1].(int); ok {
|
||||
port = arg
|
||||
}
|
||||
}
|
||||
|
||||
addr := host + ":" + com.ToStr(port)
|
||||
logger := m.Injector.GetVal(reflect.TypeOf(m.logger)).Interface().(*log.Logger)
|
||||
logger.Printf("listening on %s (%s)\n", addr, Env)
|
||||
logger.Fatalln(http.ListenAndServe(addr, m))
|
||||
}
|
||||
|
||||
// SetURLPrefix sets URL prefix of router layer, so that it support suburl.
|
||||
func (m *Macaron) SetURLPrefix(prefix string) {
|
||||
m.urlPrefix = prefix
|
||||
}
|
||||
|
||||
// ____ ____ .__ ___. .__
|
||||
// \ \ / /____ _______|__|____ \_ |__ | | ____ ______
|
||||
// \ Y /\__ \\_ __ \ \__ \ | __ \| | _/ __ \ / ___/
|
||||
// \ / / __ \| | \/ |/ __ \| \_\ \ |_\ ___/ \___ \
|
||||
// \___/ (____ /__| |__(____ /___ /____/\___ >____ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
const (
|
||||
DEV = "development"
|
||||
PROD = "production"
|
||||
TEST = "test"
|
||||
)
|
||||
|
||||
var (
|
||||
// Env is the environment that Macaron is executing in.
|
||||
// The MACARON_ENV is read on initialization to set this variable.
|
||||
Env = DEV
|
||||
|
||||
// Path of work directory.
|
||||
Root string
|
||||
|
||||
// Flash applies to current request.
|
||||
FlashNow bool
|
||||
|
||||
// Configuration convention object.
|
||||
cfg *ini.File
|
||||
)
|
||||
|
||||
func setENV(e string) {
|
||||
if len(e) > 0 {
|
||||
Env = e
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
setENV(os.Getenv("MACARON_ENV"))
|
||||
|
||||
var err error
|
||||
Root, err = os.Getwd()
|
||||
if err != nil {
|
||||
panic("error getting work directory: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfig sets data sources for configuration.
|
||||
func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err error) {
|
||||
cfg, err = ini.Load(source, others...)
|
||||
return Config(), err
|
||||
}
|
||||
|
||||
// Config returns configuration convention object.
|
||||
// It returns an empty object if there is no one available.
|
||||
func Config() *ini.File {
|
||||
if cfg == nil {
|
||||
return ini.Empty()
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
218
Godeps/_workspace/src/github.com/Unknwon/macaron/macaron_test.go
generated
vendored
Normal file
218
Godeps/_workspace/src/github.com/Unknwon/macaron/macaron_test.go
generated
vendored
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Version(t *testing.T) {
|
||||
Convey("Get version", t, func() {
|
||||
So(Version(), ShouldEqual, _VERSION)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_New(t *testing.T) {
|
||||
Convey("Initialize a new instance", t, func() {
|
||||
So(New(), ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Just test that Run doesn't bomb", t, func() {
|
||||
go New().Run()
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Setenv("PORT", "4001")
|
||||
go New().Run("0.0.0.0")
|
||||
go New().Run(4002)
|
||||
go New().Run("0.0.0.0", 4003)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_Before(t *testing.T) {
|
||||
Convey("Register before handlers", t, func() {
|
||||
m := New()
|
||||
m.Before(func(rw http.ResponseWriter, req *http.Request) bool {
|
||||
return false
|
||||
})
|
||||
m.Before(func(rw http.ResponseWriter, req *http.Request) bool {
|
||||
return true
|
||||
})
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_ServeHTTP(t *testing.T) {
|
||||
Convey("Serve HTTP requests", t, func() {
|
||||
result := ""
|
||||
m := New()
|
||||
m.Use(func(c *Context) {
|
||||
result += "foo"
|
||||
c.Next()
|
||||
result += "ban"
|
||||
})
|
||||
m.Use(func(c *Context) {
|
||||
result += "bar"
|
||||
c.Next()
|
||||
result += "baz"
|
||||
})
|
||||
m.Get("/", func() {})
|
||||
m.Action(func(res http.ResponseWriter, req *http.Request) {
|
||||
result += "bat"
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(result, ShouldEqual, "foobarbatbazban")
|
||||
So(resp.Code, ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_Handlers(t *testing.T) {
|
||||
Convey("Add custom handlers", t, func() {
|
||||
result := ""
|
||||
batman := func(c *Context) {
|
||||
result += "batman!"
|
||||
}
|
||||
|
||||
m := New()
|
||||
m.Use(func(c *Context) {
|
||||
result += "foo"
|
||||
c.Next()
|
||||
result += "ban"
|
||||
})
|
||||
m.Handlers(
|
||||
batman,
|
||||
batman,
|
||||
batman,
|
||||
)
|
||||
|
||||
Convey("Add not callable function", func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
m.Use("shit")
|
||||
})
|
||||
|
||||
m.Get("/", func() {})
|
||||
m.Action(func(res http.ResponseWriter, req *http.Request) {
|
||||
result += "bat"
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(result, ShouldEqual, "batman!batman!batman!bat")
|
||||
So(resp.Code, ShouldEqual, http.StatusBadRequest)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_EarlyWrite(t *testing.T) {
|
||||
Convey("Write early content to response", t, func() {
|
||||
result := ""
|
||||
m := New()
|
||||
m.Use(func(res http.ResponseWriter) {
|
||||
result += "foobar"
|
||||
res.Write([]byte("Hello world"))
|
||||
})
|
||||
m.Use(func() {
|
||||
result += "bat"
|
||||
})
|
||||
m.Get("/", func() {})
|
||||
m.Action(func(res http.ResponseWriter) {
|
||||
result += "baz"
|
||||
res.WriteHeader(http.StatusBadRequest)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(result, ShouldEqual, "foobar")
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_Written(t *testing.T) {
|
||||
Convey("Written sign", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
m := New()
|
||||
m.Handlers(func(res http.ResponseWriter) {
|
||||
res.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
ctx := m.createContext(resp, &http.Request{Method: "GET"})
|
||||
So(ctx.Written(), ShouldBeFalse)
|
||||
|
||||
ctx.run()
|
||||
So(ctx.Written(), ShouldBeTrue)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Macaron_Basic_NoRace(t *testing.T) {
|
||||
Convey("Make sure no race between requests", t, func() {
|
||||
m := New()
|
||||
handlers := []Handler{func() {}, func() {}}
|
||||
// Ensure append will not realloc to trigger the race condition
|
||||
m.handlers = handlers[:1]
|
||||
m.Get("/", func() {})
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
for i := 0; i < 2; i++ {
|
||||
go func() {
|
||||
resp := httptest.NewRecorder()
|
||||
m.ServeHTTP(resp, req)
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_SetENV(t *testing.T) {
|
||||
Convey("Get and save environment variable", t, func() {
|
||||
tests := []struct {
|
||||
in string
|
||||
out string
|
||||
}{
|
||||
{"", "development"},
|
||||
{"not_development", "not_development"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
setENV(test.in)
|
||||
So(Env, ShouldEqual, test.out)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Config(t *testing.T) {
|
||||
Convey("Set and get configuration object", t, func() {
|
||||
So(Config(), ShouldNotBeNil)
|
||||
cfg, err := SetConfig([]byte(""))
|
||||
So(err, ShouldBeNil)
|
||||
So(cfg, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
163
Godeps/_workspace/src/github.com/Unknwon/macaron/recovery.go
generated
vendored
Normal file
163
Godeps/_workspace/src/github.com/Unknwon/macaron/recovery.go
generated
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
||||
"github.com/Unknwon/macaron/inject"
|
||||
)
|
||||
|
||||
const (
|
||||
panicHtml = `<html>
|
||||
<head><title>PANIC: %s</title>
|
||||
<meta charset="utf-8" />
|
||||
<style type="text/css">
|
||||
html, body {
|
||||
font-family: "Roboto", sans-serif;
|
||||
color: #333333;
|
||||
background-color: #ea5343;
|
||||
margin: 0px;
|
||||
}
|
||||
h1 {
|
||||
color: #d04526;
|
||||
background-color: #ffffff;
|
||||
padding: 20px;
|
||||
border-bottom: 1px dashed #2b3848;
|
||||
}
|
||||
pre {
|
||||
margin: 20px;
|
||||
padding: 20px;
|
||||
border: 2px solid #2b3848;
|
||||
background-color: #ffffff;
|
||||
white-space: pre-wrap; /* css-3 */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 */
|
||||
white-space: -o-pre-wrap; /* Opera 7 */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
}
|
||||
</style>
|
||||
</head><body>
|
||||
<h1>PANIC</h1>
|
||||
<pre style="font-weight: bold;">%s</pre>
|
||||
<pre>%s</pre>
|
||||
</body>
|
||||
</html>`
|
||||
)
|
||||
|
||||
var (
|
||||
dunno = []byte("???")
|
||||
centerDot = []byte("·")
|
||||
dot = []byte(".")
|
||||
slash = []byte("/")
|
||||
)
|
||||
|
||||
// stack returns a nicely formated stack frame, skipping skip frames
|
||||
func stack(skip int) []byte {
|
||||
buf := new(bytes.Buffer) // the returned data
|
||||
// As we loop, we open files and read them. These variables record the currently
|
||||
// loaded file.
|
||||
var lines [][]byte
|
||||
var lastFile string
|
||||
for i := skip; ; i++ { // Skip the expected number of frames
|
||||
pc, file, line, ok := runtime.Caller(i)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
// Print this much at least. If we can't find the source, it won't show.
|
||||
fmt.Fprintf(buf, "%s:%d (0x%x)\n", file, line, pc)
|
||||
if file != lastFile {
|
||||
data, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
lines = bytes.Split(data, []byte{'\n'})
|
||||
lastFile = file
|
||||
}
|
||||
fmt.Fprintf(buf, "\t%s: %s\n", function(pc), source(lines, line))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
// source returns a space-trimmed slice of the n'th line.
|
||||
func source(lines [][]byte, n int) []byte {
|
||||
n-- // in stack trace, lines are 1-indexed but our array is 0-indexed
|
||||
if n < 0 || n >= len(lines) {
|
||||
return dunno
|
||||
}
|
||||
return bytes.TrimSpace(lines[n])
|
||||
}
|
||||
|
||||
// function returns, if possible, the name of the function containing the PC.
|
||||
func function(pc uintptr) []byte {
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
return dunno
|
||||
}
|
||||
name := []byte(fn.Name())
|
||||
// The name includes the path name to the package, which is unnecessary
|
||||
// since the file name is already included. Plus, it has center dots.
|
||||
// That is, we see
|
||||
// runtime/debug.*T·ptrmethod
|
||||
// and want
|
||||
// *T.ptrmethod
|
||||
// Also the package path might contains dot (e.g. code.google.com/...),
|
||||
// so first eliminate the path prefix
|
||||
if lastslash := bytes.LastIndex(name, slash); lastslash >= 0 {
|
||||
name = name[lastslash+1:]
|
||||
}
|
||||
if period := bytes.Index(name, dot); period >= 0 {
|
||||
name = name[period+1:]
|
||||
}
|
||||
name = bytes.Replace(name, centerDot, dot, -1)
|
||||
return name
|
||||
}
|
||||
|
||||
// Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
|
||||
// While Martini is in development mode, Recovery will also output the panic as HTML.
|
||||
func Recovery() Handler {
|
||||
return func(c *Context, log *log.Logger) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
stack := stack(3)
|
||||
log.Printf("PANIC: %s\n%s", err, stack)
|
||||
|
||||
// Lookup the current responsewriter
|
||||
val := c.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
|
||||
res := val.Interface().(http.ResponseWriter)
|
||||
|
||||
// respond with panic message while in development mode
|
||||
var body []byte
|
||||
if Env == DEV {
|
||||
res.Header().Set("Content-Type", "text/html")
|
||||
body = []byte(fmt.Sprintf(panicHtml, err, err, stack))
|
||||
}
|
||||
|
||||
res.WriteHeader(http.StatusInternalServerError)
|
||||
if nil != body {
|
||||
res.Write(body)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
74
Godeps/_workspace/src/github.com/Unknwon/macaron/recovery_test.go
generated
vendored
Normal file
74
Godeps/_workspace/src/github.com/Unknwon/macaron/recovery_test.go
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Recovery(t *testing.T) {
|
||||
Convey("Recovery from panic", t, func() {
|
||||
buf := bytes.NewBufferString("")
|
||||
setENV(DEV)
|
||||
|
||||
m := New()
|
||||
m.Map(log.New(buf, "[Macaron] ", 0))
|
||||
m.Use(func(res http.ResponseWriter, req *http.Request) {
|
||||
res.Header().Set("Content-Type", "unpredictable")
|
||||
})
|
||||
m.Use(Recovery())
|
||||
m.Use(func(res http.ResponseWriter, req *http.Request) {
|
||||
panic("here is a panic!")
|
||||
})
|
||||
m.Get("/", func() {})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
|
||||
So(resp.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html")
|
||||
So(buf.String(), ShouldNotBeEmpty)
|
||||
})
|
||||
|
||||
Convey("Revocery panic to another response writer", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
resp2 := httptest.NewRecorder()
|
||||
setENV(DEV)
|
||||
|
||||
m := New()
|
||||
m.Use(Recovery())
|
||||
m.Use(func(c *Context) {
|
||||
c.MapTo(resp2, (*http.ResponseWriter)(nil))
|
||||
panic("here is a panic!")
|
||||
})
|
||||
m.Get("/", func() {})
|
||||
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp2.Code, ShouldEqual, http.StatusInternalServerError)
|
||||
So(resp2.HeaderMap.Get("Content-Type"), ShouldEqual, "text/html")
|
||||
So(resp2.Body.Len(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
}
|
||||
624
Godeps/_workspace/src/github.com/Unknwon/macaron/render.go
generated
vendored
Normal file
624
Godeps/_workspace/src/github.com/Unknwon/macaron/render.go
generated
vendored
Normal file
@@ -0,0 +1,624 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2013 oxtoacart
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
|
||||
type BufferPool struct {
|
||||
c chan *bytes.Buffer
|
||||
}
|
||||
|
||||
// NewBufferPool creates a new BufferPool bounded to the given size.
|
||||
func NewBufferPool(size int) (bp *BufferPool) {
|
||||
return &BufferPool{
|
||||
c: make(chan *bytes.Buffer, size),
|
||||
}
|
||||
}
|
||||
|
||||
// Get gets a Buffer from the BufferPool, or creates a new one if none are available
|
||||
// in the pool.
|
||||
func (bp *BufferPool) Get() (b *bytes.Buffer) {
|
||||
select {
|
||||
case b = <-bp.c:
|
||||
// reuse existing buffer
|
||||
default:
|
||||
// create new buffer
|
||||
b = bytes.NewBuffer([]byte{})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Put returns the given Buffer to the BufferPool.
|
||||
func (bp *BufferPool) Put(b *bytes.Buffer) {
|
||||
b.Reset()
|
||||
bp.c <- b
|
||||
}
|
||||
|
||||
const (
|
||||
ContentType = "Content-Type"
|
||||
ContentLength = "Content-Length"
|
||||
ContentBinary = "application/octet-stream"
|
||||
ContentJSON = "application/json"
|
||||
ContentHTML = "text/html"
|
||||
CONTENT_PLAIN = "text/plain"
|
||||
ContentXHTML = "application/xhtml+xml"
|
||||
ContentXML = "text/xml"
|
||||
defaultCharset = "UTF-8"
|
||||
)
|
||||
|
||||
var (
|
||||
// Provides a temporary buffer to execute templates into and catch errors.
|
||||
bufpool = NewBufferPool(64)
|
||||
|
||||
// Included helper functions for use when rendering html
|
||||
helperFuncs = template.FuncMap{
|
||||
"yield": func() (string, error) {
|
||||
return "", fmt.Errorf("yield called with no layout defined")
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
type (
|
||||
// TemplateFile represents a interface of template file that has name and can be read.
|
||||
TemplateFile interface {
|
||||
Name() string
|
||||
Data() []byte
|
||||
Ext() string
|
||||
}
|
||||
// TemplateFileSystem represents a interface of template file system that able to list all files.
|
||||
TemplateFileSystem interface {
|
||||
ListFiles() []TemplateFile
|
||||
}
|
||||
|
||||
// Delims represents a set of Left and Right delimiters for HTML template rendering
|
||||
Delims struct {
|
||||
// Left delimiter, defaults to {{
|
||||
Left string
|
||||
// Right delimiter, defaults to }}
|
||||
Right string
|
||||
}
|
||||
|
||||
// RenderOptions represents a struct for specifying configuration options for the Render middleware.
|
||||
RenderOptions struct {
|
||||
// Directory to load templates. Default is "templates".
|
||||
Directory string
|
||||
// Layout template name. Will not render a layout if "". Default is to "".
|
||||
Layout string
|
||||
// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
|
||||
Extensions []string
|
||||
// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
|
||||
Funcs []template.FuncMap
|
||||
// Delims sets the action delimiters to the specified strings in the Delims struct.
|
||||
Delims Delims
|
||||
// Appends the given charset to the Content-Type header. Default is "UTF-8".
|
||||
Charset string
|
||||
// Outputs human readable JSON.
|
||||
IndentJSON bool
|
||||
// Outputs human readable XML.
|
||||
IndentXML bool
|
||||
// Prefixes the JSON output with the given bytes.
|
||||
PrefixJSON []byte
|
||||
// Prefixes the XML output with the given bytes.
|
||||
PrefixXML []byte
|
||||
// Allows changing of output to XHTML instead of HTML. Default is "text/html"
|
||||
HTMLContentType string
|
||||
// TemplateFileSystem is the interface for supporting any implmentation of template file system.
|
||||
TemplateFileSystem
|
||||
}
|
||||
|
||||
// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
|
||||
HTMLOptions struct {
|
||||
// Layout template name. Overrides Options.Layout.
|
||||
Layout string
|
||||
}
|
||||
|
||||
Render interface {
|
||||
http.ResponseWriter
|
||||
RW() http.ResponseWriter
|
||||
|
||||
JSON(int, interface{})
|
||||
JSONString(interface{}) (string, error)
|
||||
RawData(int, []byte)
|
||||
RenderData(int, []byte)
|
||||
HTML(int, string, interface{}, ...HTMLOptions)
|
||||
HTMLSet(int, string, string, interface{}, ...HTMLOptions)
|
||||
HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
|
||||
HTMLString(string, interface{}, ...HTMLOptions) (string, error)
|
||||
HTMLSetBytes(string, string, interface{}, ...HTMLOptions) ([]byte, error)
|
||||
HTMLBytes(string, interface{}, ...HTMLOptions) ([]byte, error)
|
||||
XML(int, interface{})
|
||||
Error(int, ...string)
|
||||
Status(int)
|
||||
SetTemplatePath(string, string)
|
||||
HasTemplateSet(string) bool
|
||||
}
|
||||
)
|
||||
|
||||
// TplFile implements TemplateFile interface.
|
||||
type TplFile struct {
|
||||
name string
|
||||
data []byte
|
||||
ext string
|
||||
}
|
||||
|
||||
// NewTplFile cerates new template file with given name and data.
|
||||
func NewTplFile(name string, data []byte, ext string) *TplFile {
|
||||
return &TplFile{name, data, ext}
|
||||
}
|
||||
|
||||
func (f *TplFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
func (f *TplFile) Data() []byte {
|
||||
return f.data
|
||||
}
|
||||
|
||||
func (f *TplFile) Ext() string {
|
||||
return f.ext
|
||||
}
|
||||
|
||||
// TplFileSystem implements TemplateFileSystem interface.
|
||||
type TplFileSystem struct {
|
||||
files []TemplateFile
|
||||
}
|
||||
|
||||
// NewTemplateFileSystem creates new template file system with given options.
|
||||
func NewTemplateFileSystem(opt RenderOptions, omitData bool) TplFileSystem {
|
||||
fs := TplFileSystem{}
|
||||
fs.files = make([]TemplateFile, 0, 10)
|
||||
|
||||
if err := filepath.Walk(opt.Directory, func(path string, info os.FileInfo, err error) error {
|
||||
r, err := filepath.Rel(opt.Directory, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ext := GetExt(r)
|
||||
|
||||
for _, extension := range opt.Extensions {
|
||||
if ext == extension {
|
||||
var data []byte
|
||||
if !omitData {
|
||||
data, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
name := filepath.ToSlash((r[0 : len(r)-len(ext)]))
|
||||
fs.files = append(fs.files, NewTplFile(name, data, ext))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
panic("NewTemplateFileSystem: " + err.Error())
|
||||
}
|
||||
|
||||
return fs
|
||||
}
|
||||
|
||||
func (fs TplFileSystem) ListFiles() []TemplateFile {
|
||||
return fs.files
|
||||
}
|
||||
|
||||
func PrepareCharset(charset string) string {
|
||||
if len(charset) != 0 {
|
||||
return "; charset=" + charset
|
||||
}
|
||||
|
||||
return "; charset=" + defaultCharset
|
||||
}
|
||||
|
||||
func GetExt(s string) string {
|
||||
index := strings.Index(s, ".")
|
||||
if index == -1 {
|
||||
return ""
|
||||
}
|
||||
return s[index:]
|
||||
}
|
||||
|
||||
func compile(opt RenderOptions) *template.Template {
|
||||
dir := opt.Directory
|
||||
t := template.New(dir)
|
||||
t.Delims(opt.Delims.Left, opt.Delims.Right)
|
||||
// Parse an initial template in case we don't have any.
|
||||
template.Must(t.Parse("Macaron"))
|
||||
|
||||
if opt.TemplateFileSystem == nil {
|
||||
opt.TemplateFileSystem = NewTemplateFileSystem(opt, false)
|
||||
}
|
||||
|
||||
for _, f := range opt.TemplateFileSystem.ListFiles() {
|
||||
tmpl := t.New(f.Name())
|
||||
for _, funcs := range opt.Funcs {
|
||||
tmpl.Funcs(funcs)
|
||||
}
|
||||
// Bomb out if parse fails. We don't want any silent server starts.
|
||||
template.Must(tmpl.Funcs(helperFuncs).Parse(string(f.Data())))
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
const (
|
||||
_DEFAULT_TPL_SET_NAME = "DEFAULT"
|
||||
)
|
||||
|
||||
// templateSet represents a template set of type *template.Template.
|
||||
type templateSet struct {
|
||||
lock sync.RWMutex
|
||||
sets map[string]*template.Template
|
||||
dirs map[string]string
|
||||
}
|
||||
|
||||
func newTemplateSet() *templateSet {
|
||||
return &templateSet{
|
||||
sets: make(map[string]*template.Template),
|
||||
dirs: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *templateSet) Set(name string, opt *RenderOptions) *template.Template {
|
||||
t := compile(*opt)
|
||||
|
||||
ts.lock.Lock()
|
||||
defer ts.lock.Unlock()
|
||||
|
||||
ts.sets[name] = t
|
||||
ts.dirs[name] = opt.Directory
|
||||
return t
|
||||
}
|
||||
|
||||
func (ts *templateSet) Get(name string) *template.Template {
|
||||
ts.lock.RLock()
|
||||
defer ts.lock.RUnlock()
|
||||
|
||||
return ts.sets[name]
|
||||
}
|
||||
|
||||
func (ts *templateSet) GetDir(name string) string {
|
||||
ts.lock.RLock()
|
||||
defer ts.lock.RUnlock()
|
||||
|
||||
return ts.dirs[name]
|
||||
}
|
||||
|
||||
func prepareOptions(options []RenderOptions) RenderOptions {
|
||||
var opt RenderOptions
|
||||
if len(options) > 0 {
|
||||
opt = options[0]
|
||||
}
|
||||
|
||||
// Defaults.
|
||||
if len(opt.Directory) == 0 {
|
||||
opt.Directory = "templates"
|
||||
}
|
||||
if len(opt.Extensions) == 0 {
|
||||
opt.Extensions = []string{".tmpl", ".html"}
|
||||
}
|
||||
if len(opt.HTMLContentType) == 0 {
|
||||
opt.HTMLContentType = ContentHTML
|
||||
}
|
||||
|
||||
return opt
|
||||
}
|
||||
|
||||
func ParseTplSet(tplSet string) (tplName string, tplDir string) {
|
||||
tplSet = strings.TrimSpace(tplSet)
|
||||
if len(tplSet) == 0 {
|
||||
panic("empty template set argument")
|
||||
}
|
||||
infos := strings.Split(tplSet, ":")
|
||||
if len(infos) == 1 {
|
||||
tplDir = infos[0]
|
||||
tplName = path.Base(tplDir)
|
||||
} else {
|
||||
tplName = infos[0]
|
||||
tplDir = infos[1]
|
||||
}
|
||||
|
||||
if !com.IsDir(tplDir) {
|
||||
panic("template set path does not exist or is not a directory")
|
||||
}
|
||||
return tplName, tplDir
|
||||
}
|
||||
|
||||
func renderHandler(opt RenderOptions, tplSets []string) Handler {
|
||||
cs := PrepareCharset(opt.Charset)
|
||||
ts := newTemplateSet()
|
||||
ts.Set(_DEFAULT_TPL_SET_NAME, &opt)
|
||||
|
||||
var tmpOpt RenderOptions
|
||||
for _, tplSet := range tplSets {
|
||||
tplName, tplDir := ParseTplSet(tplSet)
|
||||
tmpOpt = opt
|
||||
tmpOpt.Directory = tplDir
|
||||
ts.Set(tplName, &tmpOpt)
|
||||
}
|
||||
|
||||
return func(ctx *Context) {
|
||||
r := &TplRender{
|
||||
ResponseWriter: ctx.Resp,
|
||||
templateSet: ts,
|
||||
Opt: &opt,
|
||||
CompiledCharset: cs,
|
||||
}
|
||||
ctx.Data["TmplLoadTimes"] = func() string {
|
||||
if r.startTime.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
|
||||
}
|
||||
|
||||
ctx.Render = r
|
||||
ctx.MapTo(r, (*Render)(nil))
|
||||
}
|
||||
}
|
||||
|
||||
// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
|
||||
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
|
||||
// HTML rendering. The default directory for templates is "templates" and the default
|
||||
// file extension is ".tmpl" and ".html".
|
||||
//
|
||||
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
|
||||
// MACARON_ENV environment variable to "production".
|
||||
func Renderer(options ...RenderOptions) Handler {
|
||||
return renderHandler(prepareOptions(options), []string{})
|
||||
}
|
||||
|
||||
func Renderers(options RenderOptions, tplSets ...string) Handler {
|
||||
return renderHandler(prepareOptions([]RenderOptions{options}), tplSets)
|
||||
}
|
||||
|
||||
type TplRender struct {
|
||||
http.ResponseWriter
|
||||
*templateSet
|
||||
Opt *RenderOptions
|
||||
CompiledCharset string
|
||||
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
func (r *TplRender) RW() http.ResponseWriter {
|
||||
return r.ResponseWriter
|
||||
}
|
||||
|
||||
func (r *TplRender) JSON(status int, v interface{}) {
|
||||
var (
|
||||
result []byte
|
||||
err error
|
||||
)
|
||||
if r.Opt.IndentJSON {
|
||||
result, err = json.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = json.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// json rendered fine, write out the result
|
||||
r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
if len(r.Opt.PrefixJSON) > 0 {
|
||||
r.Write(r.Opt.PrefixJSON)
|
||||
}
|
||||
r.Write(result)
|
||||
}
|
||||
|
||||
func (r *TplRender) JSONString(v interface{}) (string, error) {
|
||||
var result []byte
|
||||
var err error
|
||||
if r.Opt.IndentJSON {
|
||||
result, err = json.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = json.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func (r *TplRender) XML(status int, v interface{}) {
|
||||
var result []byte
|
||||
var err error
|
||||
if r.Opt.IndentXML {
|
||||
result, err = xml.MarshalIndent(v, "", " ")
|
||||
} else {
|
||||
result, err = xml.Marshal(v)
|
||||
}
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), 500)
|
||||
return
|
||||
}
|
||||
|
||||
// XML rendered fine, write out the result
|
||||
r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
if len(r.Opt.PrefixXML) > 0 {
|
||||
r.Write(r.Opt.PrefixXML)
|
||||
}
|
||||
r.Write(result)
|
||||
}
|
||||
|
||||
func (r *TplRender) data(status int, contentType string, v []byte) {
|
||||
if r.Header().Get(ContentType) == "" {
|
||||
r.Header().Set(ContentType, contentType)
|
||||
}
|
||||
r.WriteHeader(status)
|
||||
r.Write(v)
|
||||
}
|
||||
|
||||
func (r *TplRender) RawData(status int, v []byte) {
|
||||
r.data(status, ContentBinary, v)
|
||||
}
|
||||
|
||||
func (r *TplRender) RenderData(status int, v []byte) {
|
||||
r.data(status, CONTENT_PLAIN, v)
|
||||
}
|
||||
|
||||
func (r *TplRender) execute(t *template.Template, name string, data interface{}) (*bytes.Buffer, error) {
|
||||
buf := bufpool.Get()
|
||||
return buf, t.ExecuteTemplate(buf, name, data)
|
||||
}
|
||||
|
||||
func (r *TplRender) addYield(t *template.Template, tplName string, data interface{}) {
|
||||
funcs := template.FuncMap{
|
||||
"yield": func() (template.HTML, error) {
|
||||
buf, err := r.execute(t, tplName, data)
|
||||
// return safe html here since we are rendering our own template
|
||||
return template.HTML(buf.String()), err
|
||||
},
|
||||
"current": func() (string, error) {
|
||||
return tplName, nil
|
||||
},
|
||||
}
|
||||
t.Funcs(funcs)
|
||||
}
|
||||
|
||||
func (r *TplRender) renderBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
|
||||
t := r.templateSet.Get(setName)
|
||||
if Env == DEV {
|
||||
opt := *r.Opt
|
||||
opt.Directory = r.templateSet.GetDir(setName)
|
||||
t = r.templateSet.Set(setName, &opt)
|
||||
}
|
||||
if t == nil {
|
||||
return nil, fmt.Errorf("html/template: template \"%s\" is undefined", tplName)
|
||||
}
|
||||
|
||||
opt := r.prepareHTMLOptions(htmlOpt)
|
||||
|
||||
if len(opt.Layout) > 0 {
|
||||
r.addYield(t, tplName, data)
|
||||
tplName = opt.Layout
|
||||
}
|
||||
|
||||
out, err := r.execute(t, tplName, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *TplRender) renderHTML(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.startTime = time.Now()
|
||||
|
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||||
if err != nil {
|
||||
http.Error(r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
|
||||
r.WriteHeader(status)
|
||||
|
||||
io.Copy(r, out)
|
||||
bufpool.Put(out)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTML(status int, name string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.renderHTML(status, _DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSet(status int, setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) {
|
||||
r.renderHTML(status, setName, tplName, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSetBytes(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||||
out, err := r.renderBytes(setName, tplName, data, htmlOpt...)
|
||||
if err != nil {
|
||||
return []byte(""), err
|
||||
}
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLBytes(name string, data interface{}, htmlOpt ...HTMLOptions) ([]byte, error) {
|
||||
return r.HTMLSetBytes(_DEFAULT_TPL_SET_NAME, name, data, htmlOpt...)
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLSetString(setName, tplName string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||||
p, err := r.HTMLSetBytes(setName, tplName, data, htmlOpt...)
|
||||
return string(p), err
|
||||
}
|
||||
|
||||
func (r *TplRender) HTMLString(name string, data interface{}, htmlOpt ...HTMLOptions) (string, error) {
|
||||
p, err := r.HTMLBytes(name, data, htmlOpt...)
|
||||
return string(p), err
|
||||
}
|
||||
|
||||
// Error writes the given HTTP status to the current ResponseWriter
|
||||
func (r *TplRender) Error(status int, message ...string) {
|
||||
r.WriteHeader(status)
|
||||
if len(message) > 0 {
|
||||
r.Write([]byte(message[0]))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TplRender) Status(status int) {
|
||||
r.WriteHeader(status)
|
||||
}
|
||||
|
||||
func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
|
||||
if len(htmlOpt) > 0 {
|
||||
return htmlOpt[0]
|
||||
}
|
||||
|
||||
return HTMLOptions{
|
||||
Layout: r.Opt.Layout,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *TplRender) SetTemplatePath(setName, dir string) {
|
||||
if len(setName) == 0 {
|
||||
setName = _DEFAULT_TPL_SET_NAME
|
||||
}
|
||||
opt := *r.Opt
|
||||
opt.Directory = dir
|
||||
r.templateSet.Set(setName, &opt)
|
||||
}
|
||||
|
||||
func (r *TplRender) HasTemplateSet(name string) bool {
|
||||
return r.templateSet.Get(name) != nil
|
||||
}
|
||||
581
Godeps/_workspace/src/github.com/Unknwon/macaron/render_test.go
generated
vendored
Normal file
581
Godeps/_workspace/src/github.com/Unknwon/macaron/render_test.go
generated
vendored
Normal file
@@ -0,0 +1,581 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type Greeting struct {
|
||||
One string `json:"one"`
|
||||
Two string `json:"two"`
|
||||
}
|
||||
|
||||
type GreetingXML struct {
|
||||
XMLName xml.Name `xml:"greeting"`
|
||||
One string `xml:"one,attr"`
|
||||
Two string `xml:"two,attr"`
|
||||
}
|
||||
|
||||
func Test_Render_JSON(t *testing.T) {
|
||||
Convey("Render JSON", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer())
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.JSON(300, Greeting{"hello", "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
|
||||
})
|
||||
|
||||
Convey("Render JSON with prefix", t, func() {
|
||||
m := Classic()
|
||||
prefix := ")]}',\n"
|
||||
m.Use(Renderer(RenderOptions{
|
||||
PrefixJSON: []byte(prefix),
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.JSON(300, Greeting{"hello", "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, prefix+`{"one":"hello","two":"world"}`)
|
||||
})
|
||||
|
||||
Convey("Render Indented JSON", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
IndentJSON: true,
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.JSON(300, Greeting{"hello", "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, `{
|
||||
"one": "hello",
|
||||
"two": "world"
|
||||
}`)
|
||||
})
|
||||
|
||||
Convey("Render JSON and return string", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer())
|
||||
m.Get("/foobar", func(r Render) {
|
||||
result, err := r.JSONString(Greeting{"hello", "world"})
|
||||
So(err, ShouldBeNil)
|
||||
So(result, ShouldEqual, `{"one":"hello","two":"world"}`)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Render with charset JSON", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Charset: "foobar",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.JSON(300, Greeting{"hello", "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentJSON+"; charset=foobar")
|
||||
So(resp.Body.String(), ShouldEqual, `{"one":"hello","two":"world"}`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_XML(t *testing.T) {
|
||||
Convey("Render XML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer())
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.XML(300, GreetingXML{One: "hello", Two: "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
|
||||
})
|
||||
|
||||
Convey("Render XML with prefix", t, func() {
|
||||
m := Classic()
|
||||
prefix := ")]}',\n"
|
||||
m.Use(Renderer(RenderOptions{
|
||||
PrefixXML: []byte(prefix),
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.XML(300, GreetingXML{One: "hello", Two: "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, prefix+`<greeting one="hello" two="world"></greeting>`)
|
||||
})
|
||||
|
||||
Convey("Render Indented XML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
IndentXML: true,
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.XML(300, GreetingXML{One: "hello", Two: "world"})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusMultipleChoices)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentXML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, `<greeting one="hello" two="world"></greeting>`)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_HTML(t *testing.T) {
|
||||
Convey("Render HTML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderers(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}, "fixtures/basic2"))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "hello", "jeremy")
|
||||
r.SetTemplatePath("", "fixtures/basic2")
|
||||
})
|
||||
m.Get("/foobar2", func(r Render) {
|
||||
if r.HasTemplateSet("basic2") {
|
||||
r.HTMLSet(200, "basic2", "hello", "jeremy")
|
||||
}
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/foobar2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
|
||||
|
||||
Convey("Change render templates path", func() {
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>What's up, jeremy</h1>")
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Render HTML and return string", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderers(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}, "basic2:fixtures/basic2"))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
result, err := r.HTMLString("hello", "jeremy")
|
||||
So(err, ShouldBeNil)
|
||||
So(result, ShouldEqual, "<h1>Hello jeremy</h1>")
|
||||
})
|
||||
m.Get("/foobar2", func(r Render) {
|
||||
result, err := r.HTMLSetString("basic2", "hello", "jeremy")
|
||||
So(err, ShouldBeNil)
|
||||
So(result, ShouldEqual, "<h1>What's up, jeremy</h1>")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/foobar2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Render with nested HTML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "admin/index", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Admin jeremy</h1>")
|
||||
})
|
||||
|
||||
Convey("Render bad HTML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "nope", nil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
|
||||
So(resp.Body.String(), ShouldEqual, "html/template: \"nope\" is undefined\n")
|
||||
})
|
||||
|
||||
Convey("Invalid template set", t, func() {
|
||||
Convey("Empty template set argument", func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
m := Classic()
|
||||
m.Use(Renderers(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}, ""))
|
||||
})
|
||||
|
||||
Convey("Bad template set path", func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
m := Classic()
|
||||
m.Use(Renderers(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}, "404"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_XHTML(t *testing.T) {
|
||||
Convey("Render XHTML", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
HTMLContentType: ContentXHTML,
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "hello", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentXHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_Extensions(t *testing.T) {
|
||||
Convey("Render with extensions", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
Extensions: []string{".tmpl", ".html"},
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "hypertext", nil)
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "Hypertext!")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_Funcs(t *testing.T) {
|
||||
Convey("Render with functions", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/custom_funcs",
|
||||
Funcs: []template.FuncMap{
|
||||
{
|
||||
"myCustomFunc": func() string {
|
||||
return "My custom function"
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "index", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Body.String(), ShouldEqual, "My custom function")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_Layout(t *testing.T) {
|
||||
Convey("Render with layout", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
Layout: "layout",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "content", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Body.String(), ShouldEqual, "head<h1>jeremy</h1>foot")
|
||||
})
|
||||
|
||||
Convey("Render with current layout", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
Layout: "current_layout",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "content", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Body.String(), ShouldEqual, "content head<h1>jeremy</h1>content foot")
|
||||
})
|
||||
|
||||
Convey("Render with override layout", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
Layout: "layout",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "content", "jeremy", HTMLOptions{
|
||||
Layout: "another_layout",
|
||||
})
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "another head<h1>jeremy</h1>another foot")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_Delimiters(t *testing.T) {
|
||||
Convey("Render with delimiters", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Delims: Delims{"{[{", "}]}"},
|
||||
Directory: "fixtures/basic",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "delims", "jeremy")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentHTML+"; charset=UTF-8")
|
||||
So(resp.Body.String(), ShouldEqual, "<h1>Hello jeremy</h1>")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_BinaryData(t *testing.T) {
|
||||
Convey("Render binary data", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer())
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.RawData(200, []byte("hello there"))
|
||||
})
|
||||
m.Get("/foobar2", func(r Render) {
|
||||
r.RenderData(200, []byte("hello there"))
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, ContentBinary)
|
||||
So(resp.Body.String(), ShouldEqual, "hello there")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/foobar2", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, CONTENT_PLAIN)
|
||||
So(resp.Body.String(), ShouldEqual, "hello there")
|
||||
})
|
||||
|
||||
Convey("Render binary data with mime type", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer())
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.RW().Header().Set(ContentType, "image/jpeg")
|
||||
r.RawData(200, []byte("..jpeg data.."))
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/foobar", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get(ContentType), ShouldEqual, "image/jpeg")
|
||||
So(resp.Body.String(), ShouldEqual, "..jpeg data..")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_Status(t *testing.T) {
|
||||
Convey("Render with status 204", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
|
||||
r.Status(204)
|
||||
So(resp.Code, ShouldEqual, http.StatusNoContent)
|
||||
})
|
||||
|
||||
Convey("Render with status 404", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
|
||||
r.Error(404)
|
||||
So(resp.Code, ShouldEqual, http.StatusNotFound)
|
||||
})
|
||||
|
||||
Convey("Render with status 500", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
r := TplRender{resp, newTemplateSet(), &RenderOptions{}, "", time.Now()}
|
||||
r.Error(500)
|
||||
So(resp.Code, ShouldEqual, http.StatusInternalServerError)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Render_NoRace(t *testing.T) {
|
||||
Convey("Make sure render has no race", t, func() {
|
||||
m := Classic()
|
||||
m.Use(Renderer(RenderOptions{
|
||||
Directory: "fixtures/basic",
|
||||
}))
|
||||
m.Get("/foobar", func(r Render) {
|
||||
r.HTML(200, "hello", "world")
|
||||
})
|
||||
|
||||
done := make(chan bool)
|
||||
doreq := func() {
|
||||
resp := httptest.NewRecorder()
|
||||
req, _ := http.NewRequest("GET", "/foobar", nil)
|
||||
m.ServeHTTP(resp, req)
|
||||
done <- true
|
||||
}
|
||||
// Run two requests to check there is no race condition
|
||||
go doreq()
|
||||
go doreq()
|
||||
<-done
|
||||
<-done
|
||||
})
|
||||
}
|
||||
|
||||
func Test_GetExt(t *testing.T) {
|
||||
Convey("Get extension", t, func() {
|
||||
So(GetExt("test"), ShouldBeBlank)
|
||||
So(GetExt("test.tmpl"), ShouldEqual, ".tmpl")
|
||||
So(GetExt("test.go.tmpl"), ShouldEqual, ".go.tmpl")
|
||||
})
|
||||
}
|
||||
@@ -42,12 +42,11 @@ type ResponseWriter interface {
|
||||
type BeforeFunc func(ResponseWriter)
|
||||
|
||||
// NewResponseWriter creates a ResponseWriter that wraps an http.ResponseWriter
|
||||
func NewResponseWriter(method string, rw http.ResponseWriter) ResponseWriter {
|
||||
return &responseWriter{method, rw, 0, 0, nil}
|
||||
func NewResponseWriter(rw http.ResponseWriter) ResponseWriter {
|
||||
return &responseWriter{rw, 0, 0, nil}
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
method string
|
||||
http.ResponseWriter
|
||||
status int
|
||||
size int
|
||||
@@ -60,15 +59,13 @@ func (rw *responseWriter) WriteHeader(s int) {
|
||||
rw.status = s
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(b []byte) (size int, err error) {
|
||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||
if !rw.Written() {
|
||||
// The status will be StatusOK if WriteHeader has not been called yet
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}
|
||||
if rw.method != "HEAD" {
|
||||
size, err = rw.ResponseWriter.Write(b)
|
||||
rw.size += size
|
||||
}
|
||||
size, err := rw.ResponseWriter.Write(b)
|
||||
rw.size += size
|
||||
return size, err
|
||||
}
|
||||
|
||||
188
Godeps/_workspace/src/github.com/Unknwon/macaron/response_writer_test.go
generated
vendored
Normal file
188
Godeps/_workspace/src/github.com/Unknwon/macaron/response_writer_test.go
generated
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type closeNotifyingRecorder struct {
|
||||
*httptest.ResponseRecorder
|
||||
closed chan bool
|
||||
}
|
||||
|
||||
func newCloseNotifyingRecorder() *closeNotifyingRecorder {
|
||||
return &closeNotifyingRecorder{
|
||||
httptest.NewRecorder(),
|
||||
make(chan bool, 1),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *closeNotifyingRecorder) close() {
|
||||
c.closed <- true
|
||||
}
|
||||
|
||||
func (c *closeNotifyingRecorder) CloseNotify() <-chan bool {
|
||||
return c.closed
|
||||
}
|
||||
|
||||
type hijackableResponse struct {
|
||||
Hijacked bool
|
||||
}
|
||||
|
||||
func newHijackableResponse() *hijackableResponse {
|
||||
return &hijackableResponse{}
|
||||
}
|
||||
|
||||
func (h *hijackableResponse) Header() http.Header { return nil }
|
||||
func (h *hijackableResponse) Write(buf []byte) (int, error) { return 0, nil }
|
||||
func (h *hijackableResponse) WriteHeader(code int) {}
|
||||
func (h *hijackableResponse) Flush() {}
|
||||
func (h *hijackableResponse) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||
h.Hijacked = true
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
func Test_ResponseWriter(t *testing.T) {
|
||||
Convey("Write string to response writer", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
rw.Write([]byte("Hello world"))
|
||||
|
||||
So(resp.Code, ShouldEqual, rw.Status())
|
||||
So(resp.Body.String(), ShouldEqual, "Hello world")
|
||||
So(rw.Status(), ShouldEqual, http.StatusOK)
|
||||
So(rw.Size(), ShouldEqual, 11)
|
||||
So(rw.Written(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Write strings to response writer", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
rw.Write([]byte("Hello world"))
|
||||
rw.Write([]byte("foo bar bat baz"))
|
||||
|
||||
So(resp.Code, ShouldEqual, rw.Status())
|
||||
So(resp.Body.String(), ShouldEqual, "Hello worldfoo bar bat baz")
|
||||
So(rw.Status(), ShouldEqual, http.StatusOK)
|
||||
So(rw.Size(), ShouldEqual, 26)
|
||||
So(rw.Written(), ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Write header to response writer", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
|
||||
So(resp.Code, ShouldEqual, rw.Status())
|
||||
So(resp.Body.String(), ShouldBeBlank)
|
||||
So(rw.Status(), ShouldEqual, http.StatusNotFound)
|
||||
So(rw.Size(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Write before response write", t, func() {
|
||||
result := ""
|
||||
resp := httptest.NewRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
rw.Before(func(ResponseWriter) {
|
||||
result += "foo"
|
||||
})
|
||||
rw.Before(func(ResponseWriter) {
|
||||
result += "bar"
|
||||
})
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
|
||||
So(resp.Code, ShouldEqual, rw.Status())
|
||||
So(resp.Body.String(), ShouldBeBlank)
|
||||
So(rw.Status(), ShouldEqual, http.StatusNotFound)
|
||||
So(rw.Size(), ShouldEqual, 0)
|
||||
So(result, ShouldEqual, "barfoo")
|
||||
})
|
||||
|
||||
Convey("Response writer with Hijack", t, func() {
|
||||
hijackable := newHijackableResponse()
|
||||
rw := NewResponseWriter(hijackable)
|
||||
hijacker, ok := rw.(http.Hijacker)
|
||||
So(ok, ShouldBeTrue)
|
||||
_, _, err := hijacker.Hijack()
|
||||
So(err, ShouldBeNil)
|
||||
So(hijackable.Hijacked, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Response writer with bad Hijack", t, func() {
|
||||
hijackable := new(http.ResponseWriter)
|
||||
rw := NewResponseWriter(*hijackable)
|
||||
hijacker, ok := rw.(http.Hijacker)
|
||||
So(ok, ShouldBeTrue)
|
||||
_, _, err := hijacker.Hijack()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Response writer with close notify", t, func() {
|
||||
resp := newCloseNotifyingRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
closed := false
|
||||
notifier := rw.(http.CloseNotifier).CloseNotify()
|
||||
resp.close()
|
||||
select {
|
||||
case <-notifier:
|
||||
closed = true
|
||||
case <-time.After(time.Second):
|
||||
}
|
||||
So(closed, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Response writer with flusher", t, func() {
|
||||
resp := httptest.NewRecorder()
|
||||
rw := NewResponseWriter(resp)
|
||||
_, ok := rw.(http.Flusher)
|
||||
So(ok, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Response writer with flusher handler", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/events", func(w http.ResponseWriter, r *http.Request) {
|
||||
f, ok := w.(http.Flusher)
|
||||
So(ok, ShouldBeTrue)
|
||||
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
io.WriteString(w, "data: Hello\n\n")
|
||||
f.Flush()
|
||||
}
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/events", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Body.String(), ShouldEqual, "data: Hello\n\ndata: Hello\n\n")
|
||||
})
|
||||
}
|
||||
59
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go
generated
vendored
Normal file
59
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/Unknwon/macaron/inject"
|
||||
)
|
||||
|
||||
// ReturnHandler is a service that Martini provides that is called
|
||||
// when a route handler returns something. The ReturnHandler is
|
||||
// responsible for writing to the ResponseWriter based on the values
|
||||
// that are passed into this function.
|
||||
type ReturnHandler func(*Context, []reflect.Value)
|
||||
|
||||
func canDeref(val reflect.Value) bool {
|
||||
return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
|
||||
}
|
||||
|
||||
func isByteSlice(val reflect.Value) bool {
|
||||
return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8
|
||||
}
|
||||
|
||||
func defaultReturnHandler() ReturnHandler {
|
||||
return func(ctx *Context, vals []reflect.Value) {
|
||||
rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
|
||||
res := rv.Interface().(http.ResponseWriter)
|
||||
var respVal reflect.Value
|
||||
if len(vals) > 1 && vals[0].Kind() == reflect.Int {
|
||||
res.WriteHeader(int(vals[0].Int()))
|
||||
respVal = vals[1]
|
||||
} else if len(vals) > 0 {
|
||||
respVal = vals[0]
|
||||
}
|
||||
if canDeref(respVal) {
|
||||
respVal = respVal.Elem()
|
||||
}
|
||||
if isByteSlice(respVal) {
|
||||
res.Write(respVal.Bytes())
|
||||
} else {
|
||||
res.Write([]byte(respVal.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Return_Handler(t *testing.T) {
|
||||
Convey("Return with status and body", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/", func() (int, string) {
|
||||
return 418, "i'm a teapot"
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusTeapot)
|
||||
So(resp.Body.String(), ShouldEqual, "i'm a teapot")
|
||||
})
|
||||
|
||||
Convey("Return with pointer", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/", func() *string {
|
||||
str := "hello world"
|
||||
return &str
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Body.String(), ShouldEqual, "hello world")
|
||||
})
|
||||
|
||||
Convey("Return with byte slice", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/", func() []byte {
|
||||
return []byte("hello world")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Body.String(), ShouldEqual, "hello world")
|
||||
})
|
||||
}
|
||||
300
Godeps/_workspace/src/github.com/Unknwon/macaron/router.go
generated
vendored
Normal file
300
Godeps/_workspace/src/github.com/Unknwon/macaron/router.go
generated
vendored
Normal file
@@ -0,0 +1,300 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
var (
|
||||
// Known HTTP methods.
|
||||
_HTTP_METHODS = map[string]bool{
|
||||
"GET": true,
|
||||
"POST": true,
|
||||
"PUT": true,
|
||||
"DELETE": true,
|
||||
"PATCH": true,
|
||||
"OPTIONS": true,
|
||||
"HEAD": true,
|
||||
}
|
||||
)
|
||||
|
||||
// routeMap represents a thread-safe map for route tree.
|
||||
type routeMap struct {
|
||||
lock sync.RWMutex
|
||||
routes map[string]map[string]bool
|
||||
}
|
||||
|
||||
// NewRouteMap initializes and returns a new routeMap.
|
||||
func NewRouteMap() *routeMap {
|
||||
rm := &routeMap{
|
||||
routes: make(map[string]map[string]bool),
|
||||
}
|
||||
for m := range _HTTP_METHODS {
|
||||
rm.routes[m] = make(map[string]bool)
|
||||
}
|
||||
return rm
|
||||
}
|
||||
|
||||
// isExist returns true if a route has been registered.
|
||||
func (rm *routeMap) isExist(method, pattern string) bool {
|
||||
rm.lock.RLock()
|
||||
defer rm.lock.RUnlock()
|
||||
|
||||
return rm.routes[method][pattern]
|
||||
}
|
||||
|
||||
// add adds new route to route tree map.
|
||||
func (rm *routeMap) add(method, pattern string) {
|
||||
rm.lock.Lock()
|
||||
defer rm.lock.Unlock()
|
||||
|
||||
rm.routes[method][pattern] = true
|
||||
}
|
||||
|
||||
type group struct {
|
||||
pattern string
|
||||
handlers []Handler
|
||||
}
|
||||
|
||||
// Router represents a Macaron router layer.
|
||||
type Router struct {
|
||||
m *Macaron
|
||||
routers map[string]*Tree
|
||||
*routeMap
|
||||
|
||||
groups []group
|
||||
notFound http.HandlerFunc
|
||||
}
|
||||
|
||||
func NewRouter() *Router {
|
||||
return &Router{
|
||||
routers: make(map[string]*Tree),
|
||||
routeMap: NewRouteMap(),
|
||||
}
|
||||
}
|
||||
|
||||
type Params map[string]string
|
||||
|
||||
// Handle is a function that can be registered to a route to handle HTTP requests.
|
||||
// Like http.HandlerFunc, but has a third parameter for the values of wildcards (variables).
|
||||
type Handle func(http.ResponseWriter, *http.Request, Params)
|
||||
|
||||
// handle adds new route to the router tree.
|
||||
func (r *Router) handle(method, pattern string, handle Handle) {
|
||||
method = strings.ToUpper(method)
|
||||
|
||||
// Prevent duplicate routes.
|
||||
if r.isExist(method, pattern) {
|
||||
return
|
||||
}
|
||||
|
||||
// Validate HTTP methods.
|
||||
if !_HTTP_METHODS[method] && method != "*" {
|
||||
panic("unknown HTTP method: " + method)
|
||||
}
|
||||
|
||||
// Generate methods need register.
|
||||
methods := make(map[string]bool)
|
||||
if method == "*" {
|
||||
for m := range _HTTP_METHODS {
|
||||
methods[m] = true
|
||||
}
|
||||
} else {
|
||||
methods[method] = true
|
||||
}
|
||||
|
||||
// Add to router tree.
|
||||
for m := range methods {
|
||||
if t, ok := r.routers[m]; ok {
|
||||
t.AddRouter(pattern, handle)
|
||||
} else {
|
||||
t := NewTree()
|
||||
t.AddRouter(pattern, handle)
|
||||
r.routers[m] = t
|
||||
}
|
||||
r.add(m, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle registers a new request handle with the given pattern, method and handlers.
|
||||
func (r *Router) Handle(method string, pattern string, handlers []Handler) {
|
||||
if len(r.groups) > 0 {
|
||||
groupPattern := ""
|
||||
h := make([]Handler, 0)
|
||||
for _, g := range r.groups {
|
||||
groupPattern += g.pattern
|
||||
h = append(h, g.handlers...)
|
||||
}
|
||||
|
||||
pattern = groupPattern + pattern
|
||||
h = append(h, handlers...)
|
||||
handlers = h
|
||||
}
|
||||
validateHandlers(handlers)
|
||||
|
||||
r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) {
|
||||
c := r.m.createContext(resp, req)
|
||||
c.params = params
|
||||
c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
|
||||
c.handlers = append(c.handlers, r.m.handlers...)
|
||||
c.handlers = append(c.handlers, handlers...)
|
||||
c.run()
|
||||
})
|
||||
}
|
||||
|
||||
func (r *Router) Group(pattern string, fn func(), h ...Handler) {
|
||||
r.groups = append(r.groups, group{pattern, h})
|
||||
fn()
|
||||
r.groups = r.groups[:len(r.groups)-1]
|
||||
}
|
||||
|
||||
// Get is a shortcut for r.Handle("GET", pattern, handlers)
|
||||
func (r *Router) Get(pattern string, h ...Handler) {
|
||||
r.Handle("GET", pattern, h)
|
||||
}
|
||||
|
||||
// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
|
||||
func (r *Router) Patch(pattern string, h ...Handler) {
|
||||
r.Handle("PATCH", pattern, h)
|
||||
}
|
||||
|
||||
// Post is a shortcut for r.Handle("POST", pattern, handlers)
|
||||
func (r *Router) Post(pattern string, h ...Handler) {
|
||||
r.Handle("POST", pattern, h)
|
||||
}
|
||||
|
||||
// Put is a shortcut for r.Handle("PUT", pattern, handlers)
|
||||
func (r *Router) Put(pattern string, h ...Handler) {
|
||||
r.Handle("PUT", pattern, h)
|
||||
}
|
||||
|
||||
// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
|
||||
func (r *Router) Delete(pattern string, h ...Handler) {
|
||||
r.Handle("DELETE", pattern, h)
|
||||
}
|
||||
|
||||
// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
|
||||
func (r *Router) Options(pattern string, h ...Handler) {
|
||||
r.Handle("OPTIONS", pattern, h)
|
||||
}
|
||||
|
||||
// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
|
||||
func (r *Router) Head(pattern string, h ...Handler) {
|
||||
r.Handle("HEAD", pattern, h)
|
||||
}
|
||||
|
||||
// Any is a shortcut for r.Handle("*", pattern, handlers)
|
||||
func (r *Router) Any(pattern string, h ...Handler) {
|
||||
r.Handle("*", pattern, h)
|
||||
}
|
||||
|
||||
// Route is a shortcut for same handlers but different HTTP methods.
|
||||
//
|
||||
// Example:
|
||||
// m.Route("/", "GET,POST", h)
|
||||
func (r *Router) Route(pattern, methods string, h ...Handler) {
|
||||
for _, m := range strings.Split(methods, ",") {
|
||||
r.Handle(strings.TrimSpace(m), pattern, h)
|
||||
}
|
||||
}
|
||||
|
||||
// Combo returns a combo router.
|
||||
func (r *Router) Combo(pattern string, h ...Handler) *ComboRouter {
|
||||
return &ComboRouter{r, pattern, h, map[string]bool{}}
|
||||
}
|
||||
|
||||
// Configurable http.HandlerFunc which is called when no matching route is
|
||||
// found. If it is not set, http.NotFound is used.
|
||||
// Be sure to set 404 response code in your handler.
|
||||
func (r *Router) NotFound(handlers ...Handler) {
|
||||
r.notFound = func(rw http.ResponseWriter, req *http.Request) {
|
||||
c := r.m.createContext(rw, req)
|
||||
c.handlers = append(r.m.handlers, handlers...)
|
||||
c.run()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if t, ok := r.routers[req.Method]; ok {
|
||||
h, p := t.Match(req.URL.Path)
|
||||
if h != nil {
|
||||
if splat, ok := p[":splat"]; ok {
|
||||
p["*"] = p[":splat"] // Better name.
|
||||
splatlist := strings.Split(splat, "/")
|
||||
for k, v := range splatlist {
|
||||
p[com.ToStr(k)] = v
|
||||
}
|
||||
}
|
||||
h(rw, req, p)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
r.notFound(rw, req)
|
||||
}
|
||||
|
||||
// ComboRouter represents a combo router.
|
||||
type ComboRouter struct {
|
||||
router *Router
|
||||
pattern string
|
||||
handlers []Handler
|
||||
methods map[string]bool // Registered methods.
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) checkMethod(name string) {
|
||||
if cr.methods[name] {
|
||||
panic("method '" + name + "' has already been registered")
|
||||
}
|
||||
cr.methods[name] = true
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) route(fn func(string, ...Handler), method string, h ...Handler) *ComboRouter {
|
||||
cr.checkMethod(method)
|
||||
fn(cr.pattern, append(cr.handlers, h...)...)
|
||||
return cr
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Get(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Get, "GET", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Patch(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Patch, "PATCH", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Post(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Post, "POST", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Put(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Put, "PUT", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Delete(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Delete, "DELETE", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Options(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Options, "OPTIONS", h...)
|
||||
}
|
||||
|
||||
func (cr *ComboRouter) Head(h ...Handler) *ComboRouter {
|
||||
return cr.route(cr.router.Head, "HEAD", h...)
|
||||
}
|
||||
199
Godeps/_workspace/src/github.com/Unknwon/macaron/router_test.go
generated
vendored
Normal file
199
Godeps/_workspace/src/github.com/Unknwon/macaron/router_test.go
generated
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
func Test_Router_Handle(t *testing.T) {
|
||||
Convey("Register all HTTP methods routes", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/get", func() string {
|
||||
return "GET"
|
||||
})
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/get", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "GET")
|
||||
|
||||
m.Patch("/patch", func() string {
|
||||
return "PATCH"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("PATCH", "/patch", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "PATCH")
|
||||
|
||||
m.Post("/post", func() string {
|
||||
return "POST"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("POST", "/post", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "POST")
|
||||
|
||||
m.Put("/put", func() string {
|
||||
return "PUT"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("PUT", "/put", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "PUT")
|
||||
|
||||
m.Delete("/delete", func() string {
|
||||
return "DELETE"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("DELETE", "/delete", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "DELETE")
|
||||
|
||||
m.Options("/options", func() string {
|
||||
return "OPTIONS"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("OPTIONS", "/options", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "OPTIONS")
|
||||
|
||||
m.Head("/head", func() string {
|
||||
return "HEAD"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("HEAD", "/head", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "HEAD")
|
||||
|
||||
m.Any("/any", func() string {
|
||||
return "ANY"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "/any", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "ANY")
|
||||
|
||||
m.Route("/route", "GET,POST", func() string {
|
||||
return "ROUTE"
|
||||
})
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("POST", "/route", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "ROUTE")
|
||||
})
|
||||
|
||||
Convey("Register all HTTP methods routes with combo", t, func() {
|
||||
m := Classic()
|
||||
m.SetURLPrefix("/prefix")
|
||||
m.Use(Renderer())
|
||||
m.Combo("/", func(ctx *Context) {
|
||||
ctx.Data["prefix"] = "Prefix_"
|
||||
}).
|
||||
Get(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "GET" }).
|
||||
Patch(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PATCH" }).
|
||||
Post(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "POST" }).
|
||||
Put(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "PUT" }).
|
||||
Delete(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "DELETE" }).
|
||||
Options(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "OPTIONS" }).
|
||||
Head(func(ctx *Context) string { return ctx.Data["prefix"].(string) + "HEAD" })
|
||||
|
||||
for name := range _HTTP_METHODS {
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest(name, "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Prefix_"+name)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
m.Combo("/").Get(func() {}).Get(nil)
|
||||
})
|
||||
|
||||
Convey("Register duplicated routes", t, func() {
|
||||
r := NewRouter()
|
||||
r.Get("/")
|
||||
r.Get("/")
|
||||
})
|
||||
|
||||
Convey("Register invalid HTTP method", t, func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
r := NewRouter()
|
||||
r.Handle("404", "/", nil)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_Group(t *testing.T) {
|
||||
Convey("Register route group", t, func() {
|
||||
m := Classic()
|
||||
m.Group("/api", func() {
|
||||
m.Group("/v1", func() {
|
||||
m.Get("/list", func() string {
|
||||
return "Well done!"
|
||||
})
|
||||
})
|
||||
})
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/api/v1/list", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Well done!")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_NotFound(t *testing.T) {
|
||||
Convey("Custom not found handler", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/", func() {})
|
||||
m.NotFound(func() string {
|
||||
return "Custom not found"
|
||||
})
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/404", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "Custom not found")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Router_splat(t *testing.T) {
|
||||
Convey("Register router with glob", t, func() {
|
||||
m := Classic()
|
||||
m.Get("/*", func(ctx *Context) string {
|
||||
return ctx.Params("*")
|
||||
})
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/hahaha", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Body.String(), ShouldEqual, "hahaha")
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 The Macaron Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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
|
||||
@@ -16,7 +16,6 @@
|
||||
package macaron
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"net/http"
|
||||
"path"
|
||||
@@ -36,9 +35,6 @@ type StaticOptions struct {
|
||||
// Expires defines which user-defined function to use for producing a HTTP Expires Header
|
||||
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
|
||||
Expires func() string
|
||||
// ETag defines if we should add an ETag header
|
||||
// https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#validating-cached-responses-with-etags
|
||||
ETag bool
|
||||
// FileSystem is the interface for supporting any implmentation of file system.
|
||||
FileSystem http.FileSystem
|
||||
}
|
||||
@@ -176,21 +172,10 @@ func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool {
|
||||
ctx.Resp.Header().Set("Expires", opt.Expires())
|
||||
}
|
||||
|
||||
if opt.ETag {
|
||||
tag := GenerateETag(string(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat))
|
||||
ctx.Resp.Header().Set("ETag", tag)
|
||||
}
|
||||
|
||||
http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
|
||||
return true
|
||||
}
|
||||
|
||||
// GenerateETag generates an ETag based on size, filename and file modification time
|
||||
func GenerateETag(fileSize, fileName, modTime string) string {
|
||||
etag := fileSize + fileName + modTime
|
||||
return base64.StdEncoding.EncodeToString([]byte(etag))
|
||||
}
|
||||
|
||||
// Static returns a middleware handler that serves static files in the given directory.
|
||||
func Static(directory string, staticOpt ...StaticOptions) Handler {
|
||||
opt := prepareStaticOptions(directory, staticOpt)
|
||||
246
Godeps/_workspace/src/github.com/Unknwon/macaron/static_test.go
generated
vendored
Normal file
246
Godeps/_workspace/src/github.com/Unknwon/macaron/static_test.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2013 Martini Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
var currentRoot, _ = os.Getwd()
|
||||
|
||||
func Test_Static(t *testing.T) {
|
||||
Convey("Serve static files", t, func() {
|
||||
m := New()
|
||||
m.Use(Static("./"))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
resp.Body = new(bytes.Buffer)
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get("Expires"), ShouldBeBlank)
|
||||
So(resp.Body.Len(), ShouldBeGreaterThan, 0)
|
||||
|
||||
Convey("Change static path", func() {
|
||||
m.Get("/", func(ctx *Context) {
|
||||
ctx.ChangeStaticPath("./", "inject")
|
||||
})
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
resp.Body = new(bytes.Buffer)
|
||||
req, err = http.NewRequest("GET", "http://localhost:4000/inject.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get("Expires"), ShouldBeBlank)
|
||||
So(resp.Body.Len(), ShouldBeGreaterThan, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Serve static files with local path", t, func() {
|
||||
Root = os.TempDir()
|
||||
f, err := ioutil.TempFile(Root, "static_content")
|
||||
So(err, ShouldBeNil)
|
||||
f.WriteString("Expected Content")
|
||||
f.Close()
|
||||
|
||||
m := New()
|
||||
m.Use(Static("."))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
resp.Body = new(bytes.Buffer)
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/"+path.Base(strings.Replace(f.Name(), "\\", "/", -1)), nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Header().Get("Expires"), ShouldBeBlank)
|
||||
So(resp.Body.String(), ShouldEqual, "Expected Content")
|
||||
})
|
||||
|
||||
Convey("Serve static files with head", t, func() {
|
||||
m := New()
|
||||
m.Use(Static(currentRoot))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
resp.Body = new(bytes.Buffer)
|
||||
req, err := http.NewRequest("HEAD", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(resp.Body.Len(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Serve static files as post", t, func() {
|
||||
m := New()
|
||||
m.Use(Static(currentRoot))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("POST", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldEqual, http.StatusNotFound)
|
||||
})
|
||||
|
||||
Convey("Serve static files with bad directory", t, func() {
|
||||
m := Classic()
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
So(resp.Code, ShouldNotEqual, http.StatusOK)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Static_Options(t *testing.T) {
|
||||
Convey("Serve static files with options logging", t, func() {
|
||||
var buf bytes.Buffer
|
||||
m := NewWithLogger(&buf)
|
||||
opt := StaticOptions{}
|
||||
m.Use(Static(currentRoot, opt))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
|
||||
|
||||
// Not disable logging.
|
||||
m.Handlers()
|
||||
buf.Reset()
|
||||
opt.SkipLogging = true
|
||||
m.Use(Static(currentRoot, opt))
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.Len(), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Serve static files with options serve index", t, func() {
|
||||
var buf bytes.Buffer
|
||||
m := NewWithLogger(&buf)
|
||||
opt := StaticOptions{IndexFile: "macaron.go"}
|
||||
m.Use(Static(currentRoot, opt))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
|
||||
})
|
||||
|
||||
Convey("Serve static files with options prefix", t, func() {
|
||||
var buf bytes.Buffer
|
||||
m := NewWithLogger(&buf)
|
||||
opt := StaticOptions{Prefix: "public"}
|
||||
m.Use(Static(currentRoot, opt))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/public/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
|
||||
})
|
||||
|
||||
Convey("Serve static files with options expires", t, func() {
|
||||
var buf bytes.Buffer
|
||||
m := NewWithLogger(&buf)
|
||||
opt := StaticOptions{Expires: func() string { return "46" }}
|
||||
m.Use(Static(currentRoot, opt))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Header().Get("Expires"), ShouldEqual, "46")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Static_Redirect(t *testing.T) {
|
||||
Convey("Serve static files with redirect", t, func() {
|
||||
m := New()
|
||||
m.Use(Static(currentRoot, StaticOptions{Prefix: "/public"}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/public", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusFound)
|
||||
So(resp.Header().Get("Location"), ShouldEqual, "/public/")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Statics(t *testing.T) {
|
||||
Convey("Serve multiple static routers", t, func() {
|
||||
Convey("Register empty directory", func() {
|
||||
defer func() {
|
||||
So(recover(), ShouldNotBeNil)
|
||||
}()
|
||||
|
||||
m := New()
|
||||
m.Use(Statics(StaticOptions{}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
Convey("Serve normally", func() {
|
||||
var buf bytes.Buffer
|
||||
m := NewWithLogger(&buf)
|
||||
m.Use(Statics(StaticOptions{}, currentRoot, currentRoot+"/inject"))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
req, err := http.NewRequest("GET", "http://localhost:4000/macaron.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.String(), ShouldEqual, "[Macaron] [Static] Serving /macaron.go\n")
|
||||
|
||||
resp = httptest.NewRecorder()
|
||||
req, err = http.NewRequest("GET", "http://localhost:4000/inject/inject.go", nil)
|
||||
So(err, ShouldBeNil)
|
||||
m.ServeHTTP(resp, req)
|
||||
|
||||
So(resp.Code, ShouldEqual, http.StatusOK)
|
||||
So(buf.String(), ShouldEndWith, "[Macaron] [Static] Serving /inject/inject.go\n")
|
||||
})
|
||||
})
|
||||
}
|
||||
421
Godeps/_workspace/src/github.com/Unknwon/macaron/tree.go
generated
vendored
Normal file
421
Godeps/_workspace/src/github.com/Unknwon/macaron/tree.go
generated
vendored
Normal file
@@ -0,0 +1,421 @@
|
||||
// Copyright 2013 Beego Authors
|
||||
// Copyright 2014 Unknwon
|
||||
//
|
||||
// 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 macaron
|
||||
|
||||
// NOTE: last sync 0c93364 on Dec 19, 2014.
|
||||
|
||||
import (
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
)
|
||||
|
||||
type leafInfo struct {
|
||||
// Names of wildcards that lead to this leaf.
|
||||
// eg, ["id" "name"] for the wildcard ":id" and ":name".
|
||||
wildcards []string
|
||||
// Not nil if the leaf is regexp.
|
||||
regexps *regexp.Regexp
|
||||
handle Handle
|
||||
}
|
||||
|
||||
func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params Params) {
|
||||
if leaf.regexps == nil {
|
||||
if len(wildcardValues) == 0 && len(leaf.wildcards) > 0 {
|
||||
if com.IsSliceContainsStr(leaf.wildcards, ":") {
|
||||
params = make(map[string]string)
|
||||
j := 0
|
||||
for _, v := range leaf.wildcards {
|
||||
if v == ":" {
|
||||
continue
|
||||
}
|
||||
params[v] = ""
|
||||
j += 1
|
||||
}
|
||||
return true, params
|
||||
}
|
||||
return false, nil
|
||||
} else if len(wildcardValues) == 0 {
|
||||
return true, nil // Static path.
|
||||
}
|
||||
|
||||
// Match *
|
||||
if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" {
|
||||
params = make(map[string]string)
|
||||
params[":splat"] = path.Join(wildcardValues...)
|
||||
return true, params
|
||||
}
|
||||
|
||||
// Match *.*
|
||||
if len(leaf.wildcards) == 3 && leaf.wildcards[0] == "." {
|
||||
params = make(map[string]string)
|
||||
lastone := wildcardValues[len(wildcardValues)-1]
|
||||
strs := strings.SplitN(lastone, ".", 2)
|
||||
if len(strs) == 2 {
|
||||
params[":ext"] = strs[1]
|
||||
} else {
|
||||
params[":ext"] = ""
|
||||
}
|
||||
params[":path"] = path.Join(wildcardValues[:len(wildcardValues)-1]...) + "/" + strs[0]
|
||||
return true, params
|
||||
}
|
||||
|
||||
// Match :id
|
||||
params = make(map[string]string)
|
||||
j := 0
|
||||
for _, v := range leaf.wildcards {
|
||||
if v == ":" {
|
||||
continue
|
||||
}
|
||||
if v == "." {
|
||||
lastone := wildcardValues[len(wildcardValues)-1]
|
||||
strs := strings.SplitN(lastone, ".", 2)
|
||||
if len(strs) == 2 {
|
||||
params[":ext"] = strs[1]
|
||||
} else {
|
||||
params[":ext"] = ""
|
||||
}
|
||||
if len(wildcardValues[j:]) == 1 {
|
||||
params[":path"] = strs[0]
|
||||
} else {
|
||||
params[":path"] = path.Join(wildcardValues[j:]...) + "/" + strs[0]
|
||||
}
|
||||
return true, params
|
||||
}
|
||||
if len(wildcardValues) <= j {
|
||||
return false, nil
|
||||
}
|
||||
params[v] = wildcardValues[j]
|
||||
j++
|
||||
}
|
||||
if len(params) != len(wildcardValues) {
|
||||
return false, nil
|
||||
}
|
||||
return true, params
|
||||
}
|
||||
|
||||
if !leaf.regexps.MatchString(path.Join(wildcardValues...)) {
|
||||
return false, nil
|
||||
}
|
||||
params = make(map[string]string)
|
||||
matches := leaf.regexps.FindStringSubmatch(path.Join(wildcardValues...))
|
||||
for i, match := range matches[1:] {
|
||||
params[leaf.wildcards[i]] = match
|
||||
}
|
||||
return true, params
|
||||
}
|
||||
|
||||
// Tree represents a router tree for Macaron instance.
|
||||
type Tree struct {
|
||||
fixroutes map[string]*Tree
|
||||
wildcard *Tree
|
||||
leaves []*leafInfo
|
||||
}
|
||||
|
||||
// NewTree initializes and returns a router tree.
|
||||
func NewTree() *Tree {
|
||||
return &Tree{
|
||||
fixroutes: make(map[string]*Tree),
|
||||
}
|
||||
}
|
||||
|
||||
// splitPath splites patthen into parts.
|
||||
//
|
||||
// Examples:
|
||||
// "/" -> []
|
||||
// "/admin" -> ["admin"]
|
||||
// "/admin/" -> ["admin"]
|
||||
// "/admin/users" -> ["admin", "users"]
|
||||
func splitPath(pattern string) []string {
|
||||
if len(pattern) == 0 {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
elements := strings.Split(pattern, "/")
|
||||
if elements[0] == "" {
|
||||
elements = elements[1:]
|
||||
}
|
||||
if elements[len(elements)-1] == "" {
|
||||
elements = elements[:len(elements)-1]
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// AddRouter adds a new route to router tree.
|
||||
func (t *Tree) AddRouter(pattern string, handle Handle) {
|
||||
t.addSegments(splitPath(pattern), handle, nil, "")
|
||||
}
|
||||
|
||||
// splitSegment splits segment into parts.
|
||||
//
|
||||
// Examples:
|
||||
// "admin" -> false, nil, ""
|
||||
// ":id" -> true, [:id], ""
|
||||
// "?:id" -> true, [: :id], "" : meaning can empty
|
||||
// ":id:int" -> true, [:id], ([0-9]+)
|
||||
// ":name:string" -> true, [:name], ([\w]+)
|
||||
// ":id([0-9]+)" -> true, [:id], ([0-9]+)
|
||||
// ":id([0-9]+)_:name" -> true, [:id :name], ([0-9]+)_(.+)
|
||||
// "cms_:id_:page.html" -> true, [:id :page], cms_(.+)_(.+).html
|
||||
// "*" -> true, [:splat], ""
|
||||
// "*.*" -> true,[. :path :ext], "" . meaning separator
|
||||
func splitSegment(key string) (bool, []string, string) {
|
||||
if strings.HasPrefix(key, "*") {
|
||||
if key == "*.*" {
|
||||
return true, []string{".", ":path", ":ext"}, ""
|
||||
} else {
|
||||
return true, []string{":splat"}, ""
|
||||
}
|
||||
}
|
||||
if strings.ContainsAny(key, ":") {
|
||||
var paramsNum int
|
||||
var out []rune
|
||||
var start bool
|
||||
var startexp bool
|
||||
var param []rune
|
||||
var expt []rune
|
||||
var skipnum int
|
||||
params := []string{}
|
||||
reg := regexp.MustCompile(`[a-zA-Z0-9]+`)
|
||||
for i, v := range key {
|
||||
if skipnum > 0 {
|
||||
skipnum -= 1
|
||||
continue
|
||||
}
|
||||
if start {
|
||||
//:id:int and :name:string
|
||||
if v == ':' {
|
||||
if len(key) >= i+4 {
|
||||
if key[i+1:i+4] == "int" {
|
||||
out = append(out, []rune("([0-9]+)")...)
|
||||
params = append(params, ":"+string(param))
|
||||
start = false
|
||||
startexp = false
|
||||
skipnum = 3
|
||||
param = make([]rune, 0)
|
||||
paramsNum += 1
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(key) >= i+7 {
|
||||
if key[i+1:i+7] == "string" {
|
||||
out = append(out, []rune(`([\w]+)`)...)
|
||||
params = append(params, ":"+string(param))
|
||||
paramsNum += 1
|
||||
start = false
|
||||
startexp = false
|
||||
skipnum = 6
|
||||
param = make([]rune, 0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
// params only support a-zA-Z0-9
|
||||
if reg.MatchString(string(v)) {
|
||||
param = append(param, v)
|
||||
continue
|
||||
}
|
||||
if v != '(' {
|
||||
out = append(out, []rune(`(.+)`)...)
|
||||
params = append(params, ":"+string(param))
|
||||
param = make([]rune, 0)
|
||||
paramsNum += 1
|
||||
start = false
|
||||
startexp = false
|
||||
}
|
||||
}
|
||||
if startexp {
|
||||
if v != ')' {
|
||||
expt = append(expt, v)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if v == ':' {
|
||||
param = make([]rune, 0)
|
||||
start = true
|
||||
} else if v == '(' {
|
||||
startexp = true
|
||||
start = false
|
||||
params = append(params, ":"+string(param))
|
||||
paramsNum += 1
|
||||
expt = make([]rune, 0)
|
||||
expt = append(expt, '(')
|
||||
} else if v == ')' {
|
||||
startexp = false
|
||||
expt = append(expt, ')')
|
||||
out = append(out, expt...)
|
||||
param = make([]rune, 0)
|
||||
} else if v == '?' {
|
||||
params = append(params, ":")
|
||||
} else {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
if len(param) > 0 {
|
||||
if paramsNum > 0 {
|
||||
out = append(out, []rune(`(.+)`)...)
|
||||
}
|
||||
params = append(params, ":"+string(param))
|
||||
}
|
||||
return true, params, string(out)
|
||||
} else {
|
||||
return false, nil, ""
|
||||
}
|
||||
}
|
||||
|
||||
// addSegments add segments to the router tree.
|
||||
func (t *Tree) addSegments(segments []string, handle Handle, wildcards []string, reg string) {
|
||||
// Fixed root route.
|
||||
if len(segments) == 0 {
|
||||
if reg != "" {
|
||||
filterCards := make([]string, 0, len(wildcards))
|
||||
for _, v := range wildcards {
|
||||
if v == ":" || v == "." {
|
||||
continue
|
||||
}
|
||||
filterCards = append(filterCards, v)
|
||||
}
|
||||
t.leaves = append(t.leaves, &leafInfo{
|
||||
handle: handle,
|
||||
wildcards: filterCards,
|
||||
regexps: regexp.MustCompile("^" + reg + "$"),
|
||||
})
|
||||
} else {
|
||||
t.leaves = append(t.leaves, &leafInfo{
|
||||
handle: handle,
|
||||
wildcards: wildcards,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
seg := segments[0]
|
||||
iswild, params, regexpStr := splitSegment(seg)
|
||||
//for the router /login/*/access match /login/2009/11/access
|
||||
if !iswild && com.IsSliceContainsStr(wildcards, ":splat") {
|
||||
iswild = true
|
||||
regexpStr = seg
|
||||
}
|
||||
if seg == "*" && len(wildcards) > 0 && reg == "" {
|
||||
iswild = true
|
||||
regexpStr = "(.+)"
|
||||
}
|
||||
if iswild {
|
||||
if t.wildcard == nil {
|
||||
t.wildcard = NewTree()
|
||||
}
|
||||
if regexpStr != "" {
|
||||
if reg == "" {
|
||||
rr := ""
|
||||
for _, w := range wildcards {
|
||||
if w == "." || w == ":" {
|
||||
continue
|
||||
}
|
||||
if w == ":splat" {
|
||||
rr = rr + "(.+)/"
|
||||
} else {
|
||||
rr = rr + "([^/]+)/"
|
||||
}
|
||||
}
|
||||
regexpStr = rr + regexpStr
|
||||
} else {
|
||||
regexpStr = "/" + regexpStr
|
||||
}
|
||||
} else if reg != "" {
|
||||
if seg == "*.*" {
|
||||
regexpStr = "/([^.]+).(.+)"
|
||||
} else {
|
||||
for _, w := range params {
|
||||
if w == "." || w == ":" {
|
||||
continue
|
||||
}
|
||||
regexpStr = "/([^/]+)" + regexpStr
|
||||
}
|
||||
}
|
||||
}
|
||||
t.wildcard.addSegments(segments[1:], handle, append(wildcards, params...), reg+regexpStr)
|
||||
} else {
|
||||
subTree, ok := t.fixroutes[seg]
|
||||
if !ok {
|
||||
subTree = NewTree()
|
||||
t.fixroutes[seg] = subTree
|
||||
}
|
||||
subTree.addSegments(segments[1:], handle, wildcards, reg)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Tree) match(segments []string, wildcardValues []string) (handle Handle, params Params) {
|
||||
// Handle leaf nodes.
|
||||
if len(segments) == 0 {
|
||||
for _, l := range t.leaves {
|
||||
if ok, pa := l.match(wildcardValues); ok {
|
||||
return l.handle, pa
|
||||
}
|
||||
}
|
||||
if t.wildcard != nil {
|
||||
for _, l := range t.wildcard.leaves {
|
||||
if ok, pa := l.match(wildcardValues); ok {
|
||||
return l.handle, pa
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
seg, segs := segments[0], segments[1:]
|
||||
|
||||
subTree, ok := t.fixroutes[seg]
|
||||
if ok {
|
||||
handle, params = subTree.match(segs, wildcardValues)
|
||||
} else if len(segs) == 0 { //.json .xml
|
||||
if subindex := strings.LastIndex(seg, "."); subindex != -1 {
|
||||
subTree, ok = t.fixroutes[seg[:subindex]]
|
||||
if ok {
|
||||
handle, params = subTree.match(segs, wildcardValues)
|
||||
if handle != nil {
|
||||
if params == nil {
|
||||
params = make(map[string]string)
|
||||
}
|
||||
params[":ext"] = seg[subindex+1:]
|
||||
return handle, params
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if handle == nil && t.wildcard != nil {
|
||||
handle, params = t.wildcard.match(segs, append(wildcardValues, seg))
|
||||
}
|
||||
if handle == nil {
|
||||
for _, l := range t.leaves {
|
||||
if ok, pa := l.match(append(wildcardValues, segments...)); ok {
|
||||
return l.handle, pa
|
||||
}
|
||||
}
|
||||
}
|
||||
return handle, params
|
||||
}
|
||||
|
||||
// Match returns Handle and params if any route is matched.
|
||||
func (t *Tree) Match(pattern string) (Handle, Params) {
|
||||
if len(pattern) == 0 || pattern[0] != '/' {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return t.match(splitPath(pattern), nil)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user