Compare commits

..

7 Commits

Author SHA1 Message Date
bergquist
be8379d1a7 docs(config): add note about poodle vulnerabillity in <3.0 2016-08-31 08:36:51 +02:00
Torkel Ödegaard
9bb4551bbf Revert "Merge pull request #4401 from dfwarden/goldap_versionbump"
This reverts commit 8f14a9ba26, reversing
changes made to 957ecabf20.
2016-03-22 09:23:13 +01:00
Torkel Ödegaard
8f14a9ba26 Merge pull request #4401 from dfwarden/goldap_versionbump
Versionbump go-ldap for nested LDAP group support
2016-03-20 10:55:37 +01:00
David Warden
109525b2a1 documentation for ldap nested groups 2016-03-17 14:25:27 -04:00
David Warden
e673e29f22 version bump go-ldap to v2.2.1 2016-03-17 14:25:00 -04:00
Mitsuhiro Tanda
957ecabf20 fix end time of Prometheus link 2016-01-14 09:07:15 +01:00
Mitsuhiro Tanda
d2955155de fix Prometheus link, expand template variable in expression 2016-01-14 09:06:28 +01:00
4181 changed files with 375554 additions and 783945 deletions

View File

@@ -1,6 +1,6 @@
[run]
init_cmds = [
["go", "build", "-o", "./bin/grafana-server", "./pkg/cmd/grafana-server"],
["go", "build", "-o", "./bin/grafana-server"],
["./bin/grafana-server"]
]
watch_all = true
@@ -12,6 +12,6 @@ watch_dirs = [
watch_exts = [".go", ".ini", ".toml", ".html"]
build_delay = 1500
cmds = [
["go", "build", "-o", "./bin/grafana-server", "./pkg/cmd/grafana-server"],
["go", "build", "-o", "./bin/grafana-server"],
["./bin/grafana-server"]
]

View File

@@ -1,13 +1,6 @@
# http://editorconfig.org
root = true
[*.go]
indent_style = tab
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*]
indent_style = space
indent_size = 2

3
.floo
View File

@@ -1,3 +0,0 @@
{
"url": "https://floobits.com/raintank/grafana"
}

View File

@@ -1,12 +0,0 @@
#*
*.o
*.pyc
*.pyo
*~
extern/
node_modules/
tmp/
data/
vendor/
public_gen/
dist/

View File

@@ -1,22 +0,0 @@
Follow the setup guide in README.md
### Rebuild frontend assets on source change
```
grunt && grunt watch
```
### Rerun tests on source change
```
grunt karma:dev
```
### Run tests for backend assets before commit
```
test -z "$(gofmt -s -l . | grep -v -E 'vendor/(github.com|golang.org|gopkg.in)' | tee /dev/stderr)"
```
### Run tests for frontend assets before commit
```
npm test
go test -v ./pkg/...
```

View File

@@ -1,18 +0,0 @@
Read before posting:
- Questions should be posted to https://community.grafana.com. Please search there and here on GitHub for similar issues before creating a new issue.
- Checkout FAQ: https://community.grafana.com/c/howto/faq
- Checkout How to troubleshoot metric query issues: https://community.grafana.com/t/how-to-troubleshoot-metric-query-issues/50
Please prefix your title with [Bug] or [Feature request].
Please include this information:
- What Grafana version are you using?
- What datasource are you using?
- What OS are you running grafana on?
- What did you do?
- What was the expected result?
- What happened instead?
- If related to metric query / data viz:
- Include raw network request & response: get by opening Chrome Dev Tools (F12, Ctrl+Shift+I on windows, Cmd+Opt+I on Mac), go the network tab.

View File

@@ -1,4 +0,0 @@
* Link the PR to an issue for new features
* Rebase your PR if it gets out of sync with master
**REMOVE THE TEXT ABOVE BEFORE CREATING THE PULL REQUEST**

12
.gitignore vendored
View File

@@ -6,15 +6,15 @@ awsconfig
/dist
/emails/dist
/public_gen
/public/vendor/npm
/tmp
vendor/phantomjs/phantomjs
vendor/phantomjs/phantomjs.exe
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
@@ -24,18 +24,12 @@ public/css/*.min.css
*.swp
.idea/
*.iml
*.tmp
.vscode/
/data/*
/bin/*
conf/custom.ini
fig.yml
docker-compose.yml
profile.cov
/grafana
grafana
.notouch
/pkg/cmd/grafana-cli/grafana-cli
/pkg/cmd/grafana-server/grafana-server
/examples/*/dist

View File

@@ -1,10 +1,7 @@
#!/usr/bin/env bash
test -z "$(gofmt -s -l . | grep -v vendor/src/ | tee /dev/stderr)"
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
grunt test

View File

@@ -10,4 +10,4 @@
"disallowSpacesInsideArrayBrackets": true,
"disallowSpacesInsideParentheses": true,
"validateIndentation": 2
}
}

View File

@@ -4,7 +4,7 @@
"bitwise":false,
"curly": true,
"eqnull": true,
"strict": true,
"globalstrict": true,
"devel": true,
"eqeqeq": true,
"forin": false,
@@ -12,7 +12,7 @@
"supernew": true,
"expr": true,
"indent": 2,
"latedef": false,
"latedef": true,
"newcap": true,
"noarg": true,
"noempty": true,
@@ -27,8 +27,6 @@
"maxlen": 140,
"globals": {
"System": true,
"Promise": true,
"define": true,
"require": true,
"Chromath": false,

View File

@@ -1,552 +1,4 @@
# 4.4.0 (unreleased)
## New Features
**Dashboard History**: View dashboard version history, compare any two versions (summary & json diffs), restore to old version. This big feature
was contributed by **Walmart Labs**. Big thanks to them for this massive contribution!
Initial feature request: [#4638](https://github.com/grafana/grafana/issues/4638)
Pull Request: [#8472](https://github.com/grafana/grafana/pull/8472)
## Enhancements
* **Elasticsearch**: Added filter aggregation label [#8420](https://github.com/grafana/grafana/pull/8420), thx [@tianzk](github.com/tianzk)
* **Sensu**: Added option for source and handler [#8405](https://github.com/grafana/grafana/pull/8405), thx [@joemiller](github.com/joemiller)
* **CSV**: Configurable csv export datetime format [#8058](https://github.com/grafana/grafana/issues/8058), thx [@cederigo](github.com/cederigo)
* **Table Panel**: Column style that preserves formatting/indentation (like pre tag) [#6617](https://github.com/grafana/grafana/issues/6617)
* **DingDing**: Add DingDing Alert Notifier [#8473](https://github.com/grafana/grafana/pull/8473) thx [@jiamliang](https://github.com/jiamliang)
## Minor Enhancements
* **Elasticsearch**: Add option for result set size in raw_document [#3426](https://github.com/grafana/grafana/issues/3426) [#8527](https://github.com/grafana/grafana/pull/8527), thx [@mk-dhia](github.com/mk-dhia)
## Bug Fixes
* **Graph**: Bug fix for negative values in histogram mode [#8628](https://github.com/grafana/grafana/issues/8628)
# 4.3.2 (2017-05-31)
## Bug fixes
* **InfluxDB**: Fixed issue with query editor not showing ALIAS BY input field when in text editor mode [#8459](https://github.com/grafana/grafana/issues/8459)
* **Graph Log Scale**: Fixed issue with log scale going below x-axis [#8244](https://github.com/grafana/grafana/issues/8244)
* **Playlist**: Fixed dashboard play order issue [#7688](https://github.com/grafana/grafana/issues/7688)
* **Elasticsearch**: Fixed table query issue with ES 2.x [#8467](https://github.com/grafana/grafana/issues/8467), thx [@goldeelox](https://github.com/goldeelox)
## Changes
* **Lazy Loading Of Panels**: Panels are no longer loaded as they are scrolled into view, this was reverted due to Chrome bug, might be reintroduced when Chrome fixes it's JS blocking behavior on scroll. [#8500](https://github.com/grafana/grafana/issues/8500)
# 4.3.1 (2017-05-23)
## Bug fixes
* **S3 image upload**: Fixed image url issue for us-east-1 (us standard) region. If you were missing slack images for alert notifications this should fix it. [#8444](https://github.com/grafana/grafana/issues/8444)
# 4.3.0-stable (2017-05-23)
## Bug fixes
* **Gzip**: Fixed crash when gzip was enabled [#8380](https://github.com/grafana/grafana/issues/8380)
* **Graphite**: Fixed issue with Toggle edit mode did in query editor [#8377](https://github.com/grafana/grafana/issues/8377)
* **Alerting**: Fixed issue with state history not showing query execution errors [#8412](https://github.com/grafana/grafana/issues/8412)
* **Alerting**: Fixed issue with missing state history events/annotations when using sqlite3 database [#7992](https://github.com/grafana/grafana/issues/7992)
* **Sqlite**: Fixed with database table locked and using sqlite3 database [#7992](https://github.com/grafana/grafana/issues/7992)
* **Alerting**: Fixed issue with annotations showing up in unsaved dashboards, new graph & alert panel. [#8361](https://github.com/grafana/grafana/issues/8361)
* **webdav**: Fixed http proxy env variable support for webdav image upload [#7922](https://github.com/grafana/grafana/issues/79222), thx [@berghauz](https://github.com/berghauz)
* **Prometheus**: Fixed issue with hiding query [#8413](https://github.com/grafana/grafana/issues/8413)
## Enhancements
* **VictorOps**: Now supports panel image & auto resolve [#8431](https://github.com/grafana/grafana/pull/8431), thx [@davidmscott](https://github.com/davidmscott)
* **Alerting**: Alert annotations now provide more info [#8421](https://github.com/grafana/grafana/pull/8421)
# 4.3.0-beta1 (2017-05-12)
## Enhancements
* **InfluxDB**: influxdb query builder support for ORDER BY and LIMIT (allows TOPN queries) [#6065](https://github.com/grafana/grafana/issues/6065) Support influxdb's SLIMIT Feature [#7232](https://github.com/grafana/grafana/issues/7232) thx [@thuck](https://github.com/thuck)
* **Panels**: Delay loading & Lazy load panels as they become visible (scrolled into view) [#5216](https://github.com/grafana/grafana/issues/5216) thx [@jifwin](https://github.com/jifwin)
* **Graph**: Support auto grid min/max when using log scale [#3090](https://github.com/grafana/grafana/issues/3090), thx [@bigbenhur](https://github.com/bigbenhur)
* **Graph**: Support for histograms [#600](https://github.com/grafana/grafana/issues/600)
* **Prometheus**: Support table response formats (column per label) [#6140](https://github.com/grafana/grafana/issues/6140), thx [@mtanda](https://github.com/mtanda)
* **Single Stat Panel**: support for non time series data [#6564](https://github.com/grafana/grafana/issues/6564)
* **Server**: Monitoring Grafana (health check endpoint) [#3302](https://github.com/grafana/grafana/issues/3302)
* **Heatmap**: Heatmap Panel [#7934](https://github.com/grafana/grafana/pull/7934)
* **Elasticsearch**: histogram aggregation [#3164](https://github.com/grafana/grafana/issues/3164)
## Minor Enhancements
* **InfluxDB**: Small fix for the "glow" when focus the field for LIMIT and SLIMIT [#7799](https://github.com/grafana/grafana/pull/7799) thx [@thuck](https://github.com/thuck)
* **Prometheus**: Make Prometheus query field a textarea [#7663](https://github.com/grafana/grafana/issues/7663), thx [@hagen1778](https://github.com/hagen1778)
* **Prometheus**: Step parameter changed semantics to min step to reduce the load on Prometheus and rendering in browser [#8073](https://github.com/grafana/grafana/pull/8073), thx [@bobrik](https://github.com/bobrik)
* **Templating**: Should not be possible to create self-referencing (recursive) template variable definitions [#7614](https://github.com/grafana/grafana/issues/7614) thx [@thuck](https://github.com/thuck)
* **Cloudwatch**: Correctly obtain IAM roles within ECS container tasks [#7892](https://github.com/grafana/grafana/issues/7892) thx [@gomlgs](https://github.com/gomlgs)
* **Units**: New number format: Scientific notation [#7781](https://github.com/grafana/grafana/issues/7781) thx [@cadnce](https://github.com/cadnce)
* **Oauth**: Add common type for oauth authorization errors [#6428](https://github.com/grafana/grafana/issues/6428) thx [@amenzhinsky](https://github.com/amenzhinsky)
* **Templating**: Data source variable now supports multi value and panel repeats [#7030](https://github.com/grafana/grafana/issues/7030) thx [@mtanda](https://github.com/mtanda)
* **Telegram**: Telegram alert is not sending metric and legend. [#8110](https://github.com/grafana/grafana/issues/8110), thx [@bashgeek](https://github.com/bashgeek)
* **Graph**: Support dashed lines [#514](https://github.com/grafana/grafana/issues/514), thx [@smalik03](https://github.com/smalik03)
* **Table**: Support to change column header text [#3551](https://github.com/grafana/grafana/issues/3551)
* **Alerting**: Better error when SMTP is not configured [#8093](https://github.com/grafana/grafana/issues/8093)
* **Pushover**: Add an option to attach graph image link in Pushover notification [#8043](https://github.com/grafana/grafana/issues/8043) thx [@devkid](https://github.com/devkid)
* **WebDAV**: Allow to set different ImageBaseUrl for WebDAV upload and image link [#7914](https://github.com/grafana/grafana/issues/7914)
* **Panels**: type-ahead mixed datasource selection [#7697](https://github.com/grafana/grafana/issues/7697) thx [@mtanda](https://github.com/mtanda)
* **Security**:User enumeration problem [#7619](https://github.com/grafana/grafana/issues/7619)
* **InfluxDB**: Register new queries available in InfluxDB - Holt Winters [#5619](https://github.com/grafana/grafana/issues/5619) thx [@rikkuness](https://github.com/rikkuness)
* **Server**: Support listening on a UNIX socket [#4030](https://github.com/grafana/grafana/issues/4030), thx [@mitjaziv](https://github.com/mitjaziv)
* **Graph**: Support log scaling for values smaller 1 [#5278](https://github.com/grafana/grafana/issues/5278)
* **InfluxDB**: Slow 'select measurement' rendering for InfluxDB [#2524](https://github.com/grafana/grafana/issues/2524), thx [@sbhenderson](https://github.com/sbhenderson)
* **Config**: Configurable signout menu activation [#7968](https://github.com/grafana/grafana/pull/7968), thx [@seuf](https://github.com/seuf)
## Fixes
* **Table Panel**: Fixed annotation display in table panel, [#8023](https://github.com/grafana/grafana/issues/8023)
* **Dashboard**: If refresh is blocked due to tab not visible, then refresh when it becomes visible [#8076](https://github.com/grafana/grafana/issues/8076) thanks [@SimenB](https://github.com/SimenB)
* **Snapshots**: Fixed problem with annotations & snapshots [#7659](https://github.com/grafana/grafana/issues/7659)
* **Graph**: MetricSegment loses type when value is an asterisk [#8277](https://github.com/grafana/grafana/issues/8277), thx [@Gordiychuk](https://github.com/Gordiychuk)
* **Alerting**: Alert notifications do not show charts when using a non public S3 bucket [#8250](https://github.com/grafana/grafana/issues/8250) thx [@rogerswingle](https://github.com/rogerswingle)
* **Graph**: 100% client CPU usage on red alert glow animation [#8222](https://github.com/grafana/grafana/issues/8222)
* **InfluxDB**: Templating: "All" query does match too much [#8165](https://github.com/grafana/grafana/issues/8165)
* **Dashboard**: Description tooltip is not fully displayed [#7970](https://github.com/grafana/grafana/issues/7970)
* **Proxy**: Redirect after switching Org does not obey sub path in root_url (using reverse proxy) [#8089](https://github.com/grafana/grafana/issues/8089)
* **Templating**: Restoration of ad-hoc variable from URL does not work correctly [#8056](https://github.com/grafana/grafana/issues/8056) thx [@tamayika](https://github.com/tamayika)
* **InfluxDB**: timeFilter cannot be used twice in alerts [#7969](https://github.com/grafana/grafana/issues/7969)
* **MySQL**: 4-byte UTF8 not supported when using MySQL database (allows Emojis) [#7958](https://github.com/grafana/grafana/issues/7958)
* **Alerting**: api/alerts and api/alert/:id hold previous data for "message" and "Message" field when field value is changed from "some string" to empty string. [#7927](https://github.com/grafana/grafana/issues/7927)
* **Graph**: Cannot add fill below to series override [#7916](https://github.com/grafana/grafana/issues/7916)
* **InfluxDB**: Influxb Datasource test passes even if the Database doesn't exist [#7864](https://github.com/grafana/grafana/issues/7864)
* **Prometheus**: Displaying Prometheus annotations is incredibly slow [#7750](https://github.com/grafana/grafana/issues/7750), thx [@mtanda](https://github.com/mtanda)
* **Graphite**: grafana generates empty find query to graphite -> 422 Unprocessable Entity [#7740](https://github.com/grafana/grafana/issues/7740)
* **Admin**: make organisation filter case insensitive [#8194](https://github.com/grafana/grafana/issues/8194), thx [@Alexander-N](https://github.com/Alexander-N)
## Changes
* **Elasticsearch**: Changed elasticsearch Terms aggregation to default to Min Doc Count to 1, and sort order to Top [#8321](https://github.com/grafana/grafana/issues/8321)
## Tech
* **Library Upgrade**: inconshreveable/log15 outdated - no support for solaris [#8262](https://github.com/grafana/grafana/issues/8262)
* **Library Upgrade**: Upgrade Macaron [#7600](https://github.com/grafana/grafana/issues/7600)
# 4.2.0 (2017-03-22)
## Minor Enhancements
* **Templates**: Prevent use of the prefix `__` for templates in web UI [#7678](https://github.com/grafana/grafana/issues/7678)
* **Threema**: Add emoji to Threema alert notifications [#7676](https://github.com/grafana/grafana/pull/7676) thx [@dbrgn](https://github.com/dbrgn)
* **Panels**: Support dm3 unit [#7695](https://github.com/grafana/grafana/issues/7695) thx [@mitjaziv](https://github.com/mitjaziv)
* **Docs**: Added some details about Sessions in Postgres [#7694](https://github.com/grafana/grafana/pull/7694) thx [@rickard-von-essen](https://github.com/rickard-von-essen)
* **Influxdb**: Allow commas in template variables [#7681](https://github.com/grafana/grafana/issues/7681) thx [@thuck](https://github.com/thuck)
* **Cloudwatch**: stop using deprecated session.New() [#7736](https://github.com/grafana/grafana/issues/7736) thx [@mtanda](https://github.com/mtanda)
* **OpenTSDB**: Pass dropcounter rate option if no max counter and no reset value or reset value as 0 is specified [#7743](https://github.com/grafana/grafana/pull/7743) thx [@r4um](https://github.com/r4um)
* **Templating**: support full resolution for $interval variable [#7696](https://github.com/grafana/grafana/pull/7696) thx [@mtanda](https://github.com/mtanda)
* **Elasticsearch**: Unique Count on string fields in ElasticSearch [#3536](https://github.com/grafana/grafana/issues/3536), thx [@pyro2927](https://github.com/pyro2927)
* **Templating**: Data source template variable that refers to other variable in regex filter [#6365](https://github.com/grafana/grafana/issues/6365) thx [@rlodge](https://github.com/rlodge)
* **Admin**: Global User List: add search and pagination [#7469](https://github.com/grafana/grafana/issues/7469)
* **User Management**: Invite UI is now disabled when login form is disabled [#7875](https://github.com/grafana/grafana/issues/7875)
## Bugfixes
* **Webhook**: Use proxy settings from environment variables [#7710](https://github.com/grafana/grafana/issues/7710)
* **Panels**: Deleting a dashboard with unsaved changes raises an error message [#7591](https://github.com/grafana/grafana/issues/7591) thx [@thuck](https://github.com/thuck)
* **Influxdb**: Query builder detects regex to easily for measurement [#7276](https://github.com/grafana/grafana/issues/7276) thx [@thuck](https://github.com/thuck)
* **Docs**: router_logging not documented [#7723](https://github.com/grafana/grafana/issues/7723)
* **Alerting**: Spelling mistake [#7739](https://github.com/grafana/grafana/pull/7739) thx [@woutersmit](https://github.com/woutersmit)
* **Alerting**: Graph legend scrolls to top when an alias is toggled/clicked [#7680](https://github.com/grafana/grafana/issues/7680) thx [@p4ddy1](https://github.com/p4ddy1)
* **Panels**: Fixed panel tooltip description after scrolling down [#7708](https://github.com/grafana/grafana/issues/7708) thx [@askomorokhov](https://github.com/askomorokhov)
# 4.2.0-beta1 (2017-02-27)
## Enhancements
* **Telegram**: Added Telegram alert notifier [#7098](https://github.com/grafana/grafana/pull/7098), thx [@leonoff](https://github.com/leonoff)
* **Templating**: Make $__interval and $__interval_ms global built in variables that can be used in by any datasource (in panel queries), closes [#7190](https://github.com/grafana/grafana/issues/7190), closes [#6582](https://github.com/grafana/grafana/issues/6582)
* **S3 Image Store**: External s3 image store (used in alert notifications) now support AWS IAM Roles, closes [#6985](https://github.com/grafana/grafana/issues/6985), [#7058](https://github.com/grafana/grafana/issues/7058) thx [@mtanda](https://github.com/mtanda)
* **SingleStat**: Implements diff aggregation method for singlestat [#7234](https://github.com/grafana/grafana/issues/7234), thx [@oliverpool](https://github.com/oliverpool)
* **Dataproxy**: Added setting to enable more verbose logging in dataproxy [#7209](https://github.com/grafana/grafana/pull/7209), thx [@Ricky-N](https://github.com/Ricky-N)
* **Alerting**: Better information about why an alert triggered [#7035](https://github.com/grafana/grafana/issues/7035)
* **LINE**: Add LINE as alerting notification channel [#7301](https://github.com/grafana/grafana/pull/7301), thx [@huydx](https://github.com/huydx)
* **LINE**: Adds image to notification message [#7417](https://github.com/grafana/grafana/pull/7417), thx [@Erliz](https://github.com/Erliz)
* **Hipchat**: Adds support for sending alert notifications to hipchat [#6451](https://github.com/grafana/grafana/issues/6451), thx [@jregovic](https://github.com/jregovic)
* **Alerting**: Uploading images for alert notifications is now optional [#7419](https://github.com/grafana/grafana/issues/7419)
* **Dashboard**: Adds shortcut for collapsing/expanding all rows [#552](https://github.com/grafana/grafana/issues/552), thx [@mtanda](https://github.com/mtanda)
* **Alerting**: Adds de duping of alert notifications [#7632](https://github.com/grafana/grafana/pull/7632)
* **Orgs**: Sharing dashboards using Grafana share feature will now redirect to correct org. [#1613](https://github.com/grafana/grafana/issues/1613)
* **Pushover**: Add Pushover alert notifications [#7526](https://github.com/grafana/grafana/pull/7526) thx [@devkid](https://github.com/devkid)
* **Threema**: Add Threema Gateway alert notification integration [#7482](https://github.com/grafana/grafana/pull/7482) thx [@dbrgn](https://github.com/dbrgn)
## Minor Enhancements
* **Optimzation**: Never issue refresh event when Grafana tab is not visible [#7218](https://github.com/grafana/grafana/issues/7218), thx [@mtanda](https://github.com/mtanda)
* **Browser History**: Browser back/forward now works time ranges / zoom, [#7259](https://github.com/grafana/grafana/issues/7259)
* **Elasticsearch**: Support for Min Doc Count options in Terms aggregation [#7324](https://github.com/grafana/grafana/pull/7324), thx [@lpic10](https://github.com/lpic10)
* **Elasticsearch**: Term aggregation limit can now be changed in template queries [#7112](https://github.com/grafana/grafana/issues/7112), thx [@FFalcon](https://github.com/FFalcon)
* **Elasticsearch**: Ad-hoc filters now support all operators [#7612](https://github.com/grafana/grafana/issues/7612), thx [@tamayika](https://github.com/tamayika)
* **Graph**: Add full series name as title for legends. [#7493](https://github.com/grafana/grafana/pull/7493), thx [@kolobaev](https://github.com/kolobaev)
* **Table**: Add a message when queries returns no data. [#6109](https://github.com/grafana/grafana/issues/6109), thx [@xginn8](https://github.com/xginn8)
* **Graph**: Set max width for series names in legend tables. [#2385](https://github.com/grafana/grafana/issues/2385), thx [@kolobaev](https://github.com/kolobaev)
* **Database**: Allow max db connection pool configuration [#7427](https://github.com/grafana/grafana/issues/7427), thx [@huydx](https://github.com/huydx)
* **Datasources** Delete datsource by name [#7476](https://github.com/grafana/grafana/issues/7476), thx [@huydx](https://github.com/huydx)
* **Dataproxy**: Only allow get that begins with api/ to access Prometheus [#7459](https://github.com/grafana/grafana/pull/7459), thx [@mtanda](https://github.com/mtanda)
* **Snapshot**: Make timeout for snapshot creation configurable [#7449](https://github.com/grafana/grafana/pull/7449) thx [@ryu1-sakai](https://github.com/ryu1-sakai)
* **Panels**: Add more physics units [#7554](https://github.com/grafana/grafana/pull/7554) thx [@ryantxu](https://github.com/ryantxu)
* **Email**: Add sender's name on email [#2131](https://github.com/grafana/grafana/issues/2131) thx [@jacobbednarz](https://github.com/jacobbednarz)
* **HTTPS**: Set tls 1.2 as lowest tls version. [#7347](https://github.com/grafana/grafana/pull/7347) thx [@roman-vynar](https://github.com/roman-vynar)
* **Table**: Added suppressing of empty results to table plugin. [#7602](https://github.com/grafana/grafana/pull/7602) thx [@LLIyRiK](https://github.com/LLIyRiK)
## Tech
* **Library Upgrade**: Upgraded angularjs from 1.5.8 to 1.6.1 [#7274](https://github.com/grafana/grafana/issues/7274)
* **Backend**: Grafana is now built using golang 1.8
## Bugfixes
* **Alerting**: Fixes missing support for no_data and execution error when testing alerts [#7149](https://github.com/grafana/grafana/issues/7149)
* **Dashboard**: Avoid duplicate data in dashboard json for panels with alerts [#7256](https://github.com/grafana/grafana/pull/7256)
* **Alertlist**: Only show scrollbar when required [#7269](https://github.com/grafana/grafana/issues/7269)
* **SMTP**: Set LocalName to hostname [#7223](https://github.com/grafana/grafana/issues/7223)
* **Sidemenu**: Disable sign out in sidemenu for AuthProxyEnabled [#7377](https://github.com/grafana/grafana/pull/7377), thx [@solugebefola](https://github.com/solugebefola)
* **Prometheus**: Add support for basic auth in Prometheus tsdb package [#6799](https://github.com/grafana/grafana/issues/6799), thx [@hagen1778](https://github.com/hagen1778)
* **OAuth**: Redirect to original page when logging in with OAuth [#7513](https://github.com/grafana/grafana/issues/7513)
* **Annotations**: Wrap text in annotations tooltip [#7542](https://github.com/grafana/grafana/pull/7542), thx [@xginn8](https://github.com/xginn8)
* **Templating**: Fixes error when using numeric sort on empty strings [#7382](https://github.com/grafana/grafana/issues/7382)
* **Templating**: Fixed issue detecting template variable dependency [#7354](https://github.com/grafana/grafana/issues/7354)
# 4.1.2 (2017-02-13)
### Bugfixes
* **Table**: Fixes broken annotation rendering mode in the table panel [#7268](https://github.com/grafana/grafana/issues/7268)
* **Data Sources**: Sorting for lists of data sources in UI is now case insensitive [#7491](https://github.com/grafana/grafana/issues/7491)
* **Admin**: Support more then 1000 users in global users list [#7469](https://github.com/grafana/grafana/issues/7469)
# 4.1.1 (2017-01-11)
### Bugfixes
* **Graph Panel**: Fixed issue with legend height in table mode [#7221](https://github.com/grafana/grafana/issues/7221)
# 4.1.0 (2017-01-11)
### Bugfixes
* **Server side PNG rendering**: Fixed issue with y-axis label rotation in phantomjs rendered images [#6924](https://github.com/grafana/grafana/issues/6924)
* **Graph**: Fixed centering of y-axis label [#7099](https://github.com/grafana/grafana/issues/7099)
* **Graph**: Fixed graph legend table mode and always visible scrollbar [#6828](https://github.com/grafana/grafana/issues/6828)
* **Templating**: Fixed template variable value groups/tags feature [#6752](https://github.com/grafana/grafana/issues/6752)
* **Webhook**: Fixed webhook username mismatch [#7195](https://github.com/grafana/grafana/pull/7195), thx [@theisenmark](https://github.com/theisenmark)
* **Influxdb**: Handles time(auto) the same way as time($interval) [#6997](https://github.com/grafana/grafana/issues/6997)
## Enhancements
* **Elasticsearch**: Added support for all moving average options [#7154](https://github.com/grafana/grafana/pull/7154), thx [@vaibhavinbayarea](https://github.com/vaibhavinbayarea)
# 4.1-beta1 (2016-12-21)
### Enhancements
* **Postgres**: Add support for Certs for Postgres database [#6655](https://github.com/grafana/grafana/issues/6655)
* **Victorops**: Add VictorOps notification integration [#6411](https://github.com/grafana/grafana/issues/6411), thx [@ichekrygin](https://github.com/ichekrygin)
* **Opsgenie**: Add OpsGenie notification integratiion [#6687](https://github.com/grafana/grafana/issues/6687), thx [@kylemcc](https://github.com/kylemcc)
* **Singlestat**: New aggregation on singlestat panel [#6740](https://github.com/grafana/grafana/pull/6740), thx [@dirk-leroux](https://github.com/dirk-leroux)
* **Cloudwatch**: Make it possible to specify access and secret key on the data source config page [#6697](https://github.com/grafana/grafana/issues/6697)
* **Table**: Added Hidden Column Style for Table Panel [#5677](https://github.com/grafana/grafana/pull/5677), thx [@bmundt](https://github.com/bmundt)
* **Graph**: Shared crosshair option renamed to shared tooltip, shows tooltip on all graphs as you hover over one graph. [#1578](https://github.com/grafana/grafana/pull/1578), [#6274](https://github.com/grafana/grafana/pull/6274)
* **Elasticsearch**: Added support for Missing option (bucket) for terms aggregation [#4244](https://github.com/grafana/grafana/pull/4244), thx [@shanielh](https://github.com/shanielh)
* **Elasticsearch**: Added support for Elasticsearch 5.x [#5740](https://github.com/grafana/grafana/issues/5740), thx [@lpic10](https://github.com/lpic10)
* **CLI**: Make it possible to reset the admin password using the grafana-cli. [#5479](https://github.com/grafana/grafana/issues/5479)
* **Influxdb**: Support multiple tags in InfluxDB annotations. [#4550](https://github.com/grafana/grafana/pull/4550), thx [@adrianlzt](https://github.com/adrianlzt)
* **LDAP**: Basic Auth now supports LDAP username and password, [#6940](https://github.com/grafana/grafana/pull/6940), thx [@utkarshcmu](https://github.com/utkarshcmu)
* **LDAP**: Now works with Auth Proxy, role and organisation mapping & sync will regularly be performed. [#6895](https://github.com/grafana/grafana/pull/6895), thx [@Seuf](https://github.com/seuf)
* **Alerting**: Adds OK as no data option. [#6866](https://github.com/grafana/grafana/issues/6866)
* **Alert list**: Order alerts based on state. [#6676](https://github.com/grafana/grafana/issues/6676)
* **Alerting**: Add api endpoint for pausing all alerts. [#6589](https://github.com/grafana/grafana/issues/6589)
* **Panel**: Added help text for panels. [#4079](https://github.com/grafana/grafana/issues/4079), thx [@utkarshcmu](https://github.com/utkarshcmu)
### Bugfixes
* **API**: HTTP API for deleting org returning incorrect message for a non-existing org [#6679](https://github.com/grafana/grafana/issues/6679)
* **Dashboard**: Posting empty dashboard result in corrupted dashboard [#5443](https://github.com/grafana/grafana/issues/5443)
* **Logging**: Fixed logging level confing issue [#6978](https://github.com/grafana/grafana/issues/6978)
* **Notifications**: Remove html escaping the email subject. [#6905](https://github.com/grafana/grafana/issues/6905)
* **Influxdb**: Fixes broken field dropdown when using template vars as measurement. [#6473](https://github.com/grafana/grafana/issues/6473)
# 4.0.2 (2016-12-08)
### Enhancements
* **Playlist**: Add support for kiosk mode [#6727](https://github.com/grafana/grafana/issues/6727)
### Bugfixes
* **Alerting**: Add alert message to webhook notifications [#6807](https://github.com/grafana/grafana/issues/6807)
* **Alerting**: Fixes a bug where avg() reducer treated null as zero. [#6879](https://github.com/grafana/grafana/issues/6879)
* **PNG Rendering**: Fix for server side rendering when using non default http addr bind and domain setting [#6813](https://github.com/grafana/grafana/issues/6813)
* **PNG Rendering**: Fix for server side rendering when setting enforce_domain to true [#6769](https://github.com/grafana/grafana/issues/6769)
* **Webhooks**: Add content type json to outgoing webhooks [#6822](https://github.com/grafana/grafana/issues/6822)
* **Keyboard shortcut**: Fixed zoom out shortcut [#6837](https://github.com/grafana/grafana/issues/6837)
* **Webdav**: Adds basic auth headers to webdav uploader [#6779](https://github.com/grafana/grafana/issues/6779)
# 4.0.1 (2016-12-02)
> **Notice**
4.0.0 had serious connection pooling issue when using a data source in proxy access. This bug caused lots of resource issues
due to too many connections/file handles on the data source backend. This problem is fixed in this release.
### Bugfixes
* **Metrics**: Fixes nil pointer dereference on my arm build [#6749](https://github.com/grafana/grafana/issues/6749)
* **Data proxy**: Fixes a tcp pooling issue in the datasource reverse proxy [#6759](https://github.com/grafana/grafana/issues/6759)
# 4.0-stable (2016-11-29)
### Bugfixes
* **Server-side rendering**: Fixed address used when rendering panel via phantomjs and using non default http_addr config [#6660](https://github.com/grafana/grafana/issues/6660)
* **Graph panel**: Fixed graph panel tooltip sort order issue [#6648](https://github.com/grafana/grafana/issues/6648)
* **Unsaved changes**: You now navigate to the intended page after saving in the unsaved changes dialog [#6675](https://github.com/grafana/grafana/issues/6675)
* **TLS Client Auth**: Support for TLS client authentication for datasource proxies [#2316](https://github.com/grafana/grafana/issues/2316)
* **Alerts out of sync**: Saving dashboards with broken alerts causes sync problem[#6576](https://github.com/grafana/grafana/issues/6576)
* **Alerting**: Saving an alert with condition "HAS NO DATA" throws an error[#6701](https://github.com/grafana/grafana/issues/6701)
* **Config**: Improve error message when parsing broken config file [#6731](https://github.com/grafana/grafana/issues/6731)
* **Table**: Render empty dates as - instead of current date [#6728](https://github.com/grafana/grafana/issues/6728)
# 4.0-beta2 (2016-11-21)
### Bugfixes
* **Graph Panel**: Log base scale on right Y-axis had no effect, max value calc was not applied, [#6534](https://github.com/grafana/grafana/issues/6534)
* **Graph Panel**: Bar width if bars was only used in series override, [#6528](https://github.com/grafana/grafana/issues/6528)
* **UI/Browser**: Fixed issue with page/view header gradient border not showing in Safari, [#6530](https://github.com/grafana/grafana/issues/6530)
* **Cloudwatch**: Fixed cloudwatch datasource requesting to many datapoints, [#6544](https://github.com/grafana/grafana/issues/6544)
* **UX**: Panel Drop zone visible after duplicating panel, and when entering fullscreen/edit view, [#6598](https://github.com/grafana/grafana/issues/6598)
* **Templating**: Newly added variable was not visible directly only after dashboard reload, [#6622](https://github.com/grafana/grafana/issues/6622)
### Enhancements
* **Singlestat**: Support repeated template variables in prefix/postfix [#6595](https://github.com/grafana/grafana/issues/6595)
* **Templating**: Don't persist variable options with refresh option [#6586](https://github.com/grafana/grafana/issues/6586)
* **Alerting**: Add ability to have OR conditions (and mixing AND & OR) [#6579](https://github.com/grafana/grafana/issues/6579)
* **InfluxDB**: Fix for Ad-Hoc Filters variable & changing dashboards [#6821](https://github.com/grafana/grafana/issues/6821)
# 4.0-beta1 (2016-11-09)
### Enhancements
* **Login**: Adds option to disable username/password logins, closes [#4674](https://github.com/grafana/grafana/issues/4674)
* **SingleStat**: Add seriename as option in singlestat panel, closes [#4740](https://github.com/grafana/grafana/issues/4740)
* **Localization**: Week start day now dependant on browser locale setting, closes [#3003](https://github.com/grafana/grafana/issues/3003)
* **Templating**: Update panel repeats for variables that change on time refresh, closes [#5021](https://github.com/grafana/grafana/issues/5021)
* **Templating**: Add support for numeric and alphabetical sorting of variable values, closes [#2839](https://github.com/grafana/grafana/issues/2839)
* **Elasticsearch**: Support to set Precision Threshold for Unique Count metric, closes [#4689](https://github.com/grafana/grafana/issues/4689)
* **Navigation**: Add search to org swithcer, closes [#2609](https://github.com/grafana/grafana/issues/2609)
* **Database**: Allow database config using one propertie, closes [#5456](https://github.com/grafana/grafana/pull/5456)
* **Graphite**: Add support for groupByNodes, closes [#5613](https://github.com/grafana/grafana/pull/5613)
* **Influxdb**: Add support for elapsed(), closes [#5827](https://github.com/grafana/grafana/pull/5827)
* **OpenTSDB**: Add support for explicitTags for OpenTSDB>=2.3, closes [#6360](https://github.com/grafana/grafana/pull/6361)
* **OAuth**: Add support for generic oauth, closes [#4718](https://github.com/grafana/grafana/pull/4718)
* **Cloudwatch**: Add support to expand multi select template variable, closes [#5003](https://github.com/grafana/grafana/pull/5003)
* **Background Tasks**: Now support automatic purging of old snapshots, closes [#4087](https://github.com/grafana/grafana/issues/4087)
* **Background Tasks**: Now support automatic purging of old rendered images, closes [#2172](https://github.com/grafana/grafana/issues/2172)
* **Dashboard**: After inactivity hide nav/row actions, fade to nice clean view, can be toggled with `d v`, also added kiosk mode, toggled via `d k` [#6476](https://github.com/grafana/grafana/issues/6476)
* **Dashboard**: Improved dashboard row menu & add panel UX [#6442](https://github.com/grafana/grafana/issues/6442)
* **Graph Panel**: Support for stacking null values [#2912](https://github.com/grafana/grafana/issues/2912), [#6287](https://github.com/grafana/grafana/issues/6287), thanks @benrubson!
### Breaking changes
* **SystemD**: Change systemd description, closes [#5971](https://github.com/grafana/grafana/pull/5971)
* **lodash upgrade**: Upgraded lodash from 2.4.2 to 4.15.0, this contains a number of breaking changes that could effect plugins. closes [#6021](https://github.com/grafana/grafana/pull/6021)
### Bug fixes
* **Table Panel**: Fixed problem when switching to Mixed datasource in metrics tab, fixes [#5999](https://github.com/grafana/grafana/pull/5999)
* **Playlist**: Fixed problem with play order not matching order defined in playlist, fixes [#5467](https://github.com/grafana/grafana/pull/5467)
* **Graph panel**: Fixed problem with auto decimals on y axis when datamin=datamax, fixes [#6070](https://github.com/grafana/grafana/pull/6070)
* **Snapshot**: Can view embedded panels/png rendered panels in snapshots without login, fixes [#3769](https://github.com/grafana/grafana/pull/3769)
* **Elasticsearch**: Fix for query template variable when looking up terms without query, no longer relies on elasticsearch default field, fixes [#3887](https://github.com/grafana/grafana/pull/3887)
* **Elasticsearch**: Fix for displaying IP address used in terms aggregations, fixes [#4393](https://github.com/grafana/grafana/pull/4393)
* **PNG Rendering**: Fix for server side rendering when using auth proxy, fixes [#5906](https://github.com/grafana/grafana/pull/5906)
* **OpenTSDB**: Fixed multi-value nested templating for opentsdb, fixes [#6455](https://github.com/grafana/grafana/pull/6455)
* **Playlist**: Remove playlist items when dashboard is removed, fixes [#6292](https://github.com/grafana/grafana/issues/6292)
# 3.1.2 (unreleased)
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
* **Drag&Drop**: Fixed issue with drag and drop in latest Chrome(51+), fixes [#5767](https://github.com/grafana/grafana/issues/5767)
* **Internal Metrics**: Fixed issue with dots in instance_name when sending internal metrics to Graphite, fixes [#5739](https://github.com/grafana/grafana/issues/5739)
* **Grafana-CLI**: Add default plugin path for MAC OS, fixes [#5806](https://github.com/grafana/grafana/issues/5806)
* **Grafana-CLI**: Improve error message for upgrade-all command, fixes [#5885](https://github.com/grafana/grafana/issues/5885)
# 3.1.1 (2016-08-01)
* **IFrame embedding**: Fixed issue of using full iframe height, fixes [#5605](https://github.com/grafana/grafana/issues/5606)
* **Panel PNG rendering**: Fixed issue detecting render completion, fixes [#5605](https://github.com/grafana/grafana/issues/5606)
* **Elasticsearch**: Fixed issue with templating query and json parse error, fixes [#5615](https://github.com/grafana/grafana/issues/5615)
* **Tech**: Upgraded JQuery to 2.2.4 to fix Security vulnerabilitie in 2.1.4, fixes [#5627](https://github.com/grafana/grafana/issues/5627)
* **Graphite**: Fixed issue with mixed data sources and Graphite, fixes [#5617](https://github.com/grafana/grafana/issues/5617)
* **Templating**: Fixed issue with template variable query was issued multiple times during dashboard load, fixes [#5637](https://github.com/grafana/grafana/issues/5637)
* **Zoom**: Fixed issues with zoom in and out on embedded (iframed) panel, fixes [#4489](https://github.com/grafana/grafana/issues/4489), [#5666](https://github.com/grafana/grafana/issues/5666)
# 3.1.0 stable (2016-07-12)
### Bugfixes & Enhancements,
* **User Alert Notices**: Backend error alert popups did not show properly, fixes [#5435](https://github.com/grafana/grafana/issues/5435)
* **Table**: Added sanitize HTML option to allow links in table cells, fixes [#4596](https://github.com/grafana/grafana/issues/4596)
* **Apps**: App dashboards are automatically synced to DB at startup after plugin update, fixes [#5529](https://github.com/grafana/grafana/issues/5529)
# 3.1.0-beta1 (2016-06-23)
### Enhancements
* **Dashboard Export/Import**: Dashboard export now templetize data sources and constant variables, users pick these on import, closes [#5084](https://github.com/grafana/grafana/issues/5084)
* **Dashboard Url**: Time range changes updates url, closes [#458](https://github.com/grafana/grafana/issues/458)
* **Dashboard Url**: Template variable change updates url, closes [#5002](https://github.com/grafana/grafana/issues/5002)
* **Singlestat**: Add support for range to text mappings, closes [#1319](https://github.com/grafana/grafana/issues/1319)
* **Graph**: Adds sort order options for graph tooltip, closes [#1189](https://github.com/grafana/grafana/issues/1189)
* **Theme**: Add default theme to config file [#5011](https://github.com/grafana/grafana/pull/5011)
* **Page Footer**: Added page footer with links to docs, shows Grafana version and info if new version is available, closes [#4889](https://github.com/grafana/grafana/pull/4889)
* **InfluxDB**: Add spread function, closes [#5211](https://github.com/grafana/grafana/issues/5211)
* **Scripts**: Use restart instead of start for deb package script, closes [#5282](https://github.com/grafana/grafana/pull/5282)
* **Logging**: Moved to structured logging lib, and moved to component specific level filters via config file, closes [#4590](https://github.com/grafana/grafana/issues/4590)
* **OpenTSDB**: Support nested template variables in tag_values function, closes [#4398](https://github.com/grafana/grafana/issues/4398)
* **Datasource**: Pending data source requests are cancelled before new ones are issues (Graphite & Prometheus), closes [#5321](https://github.com/grafana/grafana/issues/5321)
### Breaking changes
* **Logging** : Changed default logging output format (now structured into message, and key value pairs, with logger key acting as component). You can also no change in config to json log ouput.
* **Graphite** : The Graph panel no longer have a Graphite PNG option. closes [#5367](https://github.com/grafana/grafana/issues/5367)
### Bug fixes
* **PNG rendering**: Fixed phantomjs rendering and y-axis label rotation. fixes [#5220](https://github.com/grafana/grafana/issues/5220)
* **CLI**: The cli tool now supports reading plugin.json from dist/plugin.json. fixes [#5410](https://github.com/grafana/grafana/issues/5410)
# 3.0.4 Patch release (2016-05-25)
* **Panel**: Fixed blank dashboard issue when switching to other dashboard while in fullscreen edit mode, fixes [#5163](https://github.com/grafana/grafana/pull/5163)
* **Templating**: Fixed issue with nested multi select variables and cascading and updating child variable selection state, fixes [#4861](https://github.com/grafana/grafana/pull/4861)
* **Templating**: Fixed issue with using templated data source in another template variable query, fixes [#5165](https://github.com/grafana/grafana/pull/5165)
* **Singlestat gauge**: Fixed issue with gauge render position, fixes [#5143](https://github.com/grafana/grafana/pull/5143)
* **Home dashboard**: Fixes broken home dashboard api, fixes [#5167](https://github.com/grafana/grafana/issues/5167)
# 3.0.3 Patch release (2016-05-23)
* **Annotations**: Annotations can now use a template variable as data source, closes [#5054](https://github.com/grafana/grafana/issues/5054)
* **Time picker**: Fixed issue timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
* **CloudWatch**: Support for Multiple Account by AssumeRole, closes [#3522](https://github.com/grafana/grafana/issues/3522)
* **Singlestat**: Fixed alignment and minium height issue, fixes [#5113](https://github.com/grafana/grafana/issues/5113), fixes [#4679](https://github.com/grafana/grafana/issues/4679)
* **Share modal**: Fixed link when using grafana under dashboard sub url, fixes [#5109](https://github.com/grafana/grafana/issues/5109)
* **Prometheus**: Fixed bug in query editor that caused it not to load when reloading page, fixes [#5107](https://github.com/grafana/grafana/issues/5107)
* **Elasticsearch**: Fixed bug when template variable query returns numeric values, fixes [#5097](https://github.com/grafana/grafana/issues/5097), fixes [#5088](https://github.com/grafana/grafana/issues/5088)
* **Logging**: Fixed issue with reading logging level value, fixes [#5079](https://github.com/grafana/grafana/issues/5079)
* **Timepicker**: Fixed issue with timepicker and UTC when reading time from URL, fixes [#5078](https://github.com/grafana/grafana/issues/5078)
* **Docs**: Added docs for org & user preferences HTTP API, closes [#5069](https://github.com/grafana/grafana/issues/5069)
* **Plugin list panel**: Now shows correct enable state for apps when not enabled, fixes [#5068](https://github.com/grafana/grafana/issues/5068)
* **Elasticsearch**: Templating & Annotation queries that use template variables are now formatted correctly, fixes [#5135](https://github.com/grafana/grafana/issues/5135)
# 3.0.2 Patch release (2016-05-16)
* **Templating**: Fixed issue mixing row repeat and panel repeats, fixes [#4988](https://github.com/grafana/grafana/issues/4988)
* **Templating**: Fixed issue detecting dependencies in nested variables, fixes [#4987](https://github.com/grafana/grafana/issues/4987), fixes [#4986](https://github.com/grafana/grafana/issues/4986)
* **Graph**: Fixed broken PNG rendering in graph panel, fixes [#5025](https://github.com/grafana/grafana/issues/5025)
* **Graph**: Fixed broken xaxis on graph panel, fixes [#5024](https://github.com/grafana/grafana/issues/5024)
* **Influxdb**: Fixes crash when hiding middle serie, fixes [#5005](https://github.com/grafana/grafana/issues/5005)
# 3.0.1 Stable (2016-05-11)
### Bug fixes
* **Templating**: Fixed issue with new data source variable not persisting current selected value, fixes [#4934](https://github.com/grafana/grafana/issues/4934)
# 3.0.0-beta7 (2016-05-02)
### Bug fixes
* **Dashboard title**: Fixed max dashboard title width (media query) for large screens, fixes [#4859](https://github.com/grafana/grafana/issues/4859)
* **Annotations**: Fixed issue with entering annotation edit view, fixes [#4857](https://github.com/grafana/grafana/issues/4857)
* **Remove query**: Fixed issue with removing query for data sources without collapsable query editors, fixes [#4856](https://github.com/grafana/grafana/issues/4856)
* **Graphite PNG**: Fixed issue graphite png rendering option, fixes [#4864](https://github.com/grafana/grafana/issues/4864)
* **InfluxDB**: Fixed issue missing plus group by iconn, fixes [#4862](https://github.com/grafana/grafana/issues/4862)
* **Graph**: Fixes missing line mode for thresholds, fixes [#4902](https://github.com/grafana/grafana/pull/4902)
### Enhancements
* **InfluxDB**: Added new functions moving_average and difference to query editor, closes [#4698](https://github.com/grafana/grafana/issues/4698)
# 3.0.0-beta6 (2016-04-29)
### Enhancements
* **Singlestat**: Support for gauges in singlestat panel. closes [#3688](https://github.com/grafana/grafana/pull/3688)
* **Templating**: Support for data source as variable, closes [#816](https://github.com/grafana/grafana/pull/816)
### Bug fixes
* **InfluxDB 0.12**: Fixed issue templating and `show tag values` query only returning tags for first measurement, fixes [#4726](https://github.com/grafana/grafana/issues/4726)
* **Templating**: Fixed issue with regex formating when matching multiple values, fixes [#4755](https://github.com/grafana/grafana/issues/4755)
* **Templating**: Fixed issue with custom all value and escaping, fixes [#4736](https://github.com/grafana/grafana/issues/4736)
* **Dashlist**: Fixed issue dashboard list panel and caching tags, fixes [#4768](https://github.com/grafana/grafana/issues/4768)
* **Graph**: Fixed issue with unneeded scrollbar in legend for Firefox, fixes [#4760](https://github.com/grafana/grafana/issues/4760)
* **Table panel**: Fixed issue table panel formating string array properties, fixes [#4791](https://github.com/grafana/grafana/issues/4791)
* **grafana-cli**: Improve error message when failing to install plugins due to corrupt response, fixes [#4651](https://github.com/grafana/grafana/issues/4651)
* **Singlestat**: Fixes prefix an postfix for gauges, fixes [#4812](https://github.com/grafana/grafana/issues/4812)
* **Singlestat**: Fixes auto-refresh on change for some options, fixes [#4809](https://github.com/grafana/grafana/issues/4809)
### Breaking changes
**Data Source Query Editors**: Issue [#3900](https://github.com/grafana/grafana/issues/3900)
Query editors have been updated to use the new form styles. External data source plugins needs to be
updated to work. Sorry to introduce breaking change this late in beta phase. We wanted to get this change
in before 3.0 stable is released so we don't have to break data sources in next release (3.1). If you are
a data source plugin author and want help for how the new form styles work please ask for help in
slack channel (link to slack channel in readme).
# 3.0.0-beta5 (2016-04-15)
### Bug fixes
* **grafana-cli**: Fixed issue grafana-cli tool, did not detect the right plugin dir, fixes [#4723](https://github.com/grafana/grafana/issues/4723)
* **Graph**: Fixed issue with light theme text color issue in tooltip, fixes [#4702](https://github.com/grafana/grafana/issues/4702)
* **Snapshot**: Fixed issue with empty snapshots, fixes [#4706](https://github.com/grafana/grafana/issues/4706)
# 3.0.0-beta4 (2016-04-13)
### Bug fixes
* **Home dashboard**: Fixed issue with permission denied error on home dashboard, fixes [#4686](https://github.com/grafana/grafana/issues/4686)
* **Templating**: Fixed issue templating variables that use regex extraction, fixes [#4672](https://github.com/grafana/grafana/issues/4672)
# 3.0.0-beta3 (2016-04-12)
### Enhancements
* **InfluxDB**: Changed multi query encoding to work with InfluxDB 0.11 & 0.12, closes [#4533](https://github.com/grafana/grafana/issues/4533)
* **Timepicker**: Add arrows and shortcuts for moving back and forth in current dashboard, closes [#119](https://github.com/grafana/grafana/issues/119)
### Bug fixes
* **Postgres**: Fixed page render crash when using postgres, fixes [#4558](https://github.com/grafana/grafana/issues/4558)
* **Table panel**: Fixed table panel bug when trying to show annotations in table panel, fixes [#4563](https://github.com/grafana/grafana/issues/4563)
* **App Config**: Fixed app config issue showing content of other app config, fixes [#4575](https://github.com/grafana/grafana/issues/4575)
* **Graph Panel**: Fixed legend option max not updating, fixes [#4601](https://github.com/grafana/grafana/issues/4601)
* **Graph Panel**: Fixed issue where newly added graph panels shared same axes config, fixes [#4582](https://github.com/grafana/grafana/issues/4582)
* **Graph Panel**: Fixed issue with axis labels overlapping Y-axis, fixes [#4626](https://github.com/grafana/grafana/issues/4626)
* **InfluxDB**: Fixed issue with templating query containing template variable, fixes [#4602](https://github.com/grafana/grafana/issues/4602)
* **Graph Panel**: Fixed issue with hiding series and stacking, fixes [#4557](https://github.com/grafana/grafana/issues/4557)
* **Graph Panel**: Fixed issue with legend height in table mode with few series, affected iframe embedding as well, fixes [#4640](https://github.com/grafana/grafana/issues/4640)
# 3.0.0-beta2 (2016-04-04)
### New Features (introduces since 3.0-beta1)
* **Preferences**: Set home dashboard on user and org level, closes [#1678](https://github.com/grafana/grafana/issues/1678)
* **Preferences**: Set timezone on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1200](https://github.com/grafana/grafana/issues/1200)
* **Preferences**: Set theme on user and org level, closes [#3214](https://github.com/grafana/grafana/issues/3214), [#1917](https://github.com/grafana/grafana/issues/1917)
### Bug fixes
* **Dashboard**: Fixed dashboard panel layout for mobile devices, fixes [#4529](https://github.com/grafana/grafana/issues/4529)
* **Table Panel**: Fixed issue with table panel sort, fixes [#4532](https://github.com/grafana/grafana/issues/4532)
* **Page Load Crash**: A Datasource with null jsonData would make Grafana fail to load page, fixes [#4536](https://github.com/grafana/grafana/issues/4536)
* **Metrics tab**: Fix for missing datasource name in datasource selector, fixes [#4541](https://github.com/grafana/grafana/issues/4540)
* **Graph**: Fix legend in table mode with series on right-y axis, fixes [#4551](https://github.com/grafana/grafana/issues/4551), [#1145](https://github.com/grafana/grafana/issues/1145)
# 3.0.0-beta1 (2016-03-31)
### New Features
* **Playlists**: Playlists can now be persisted and started from urls, closes [#3655](https://github.com/grafana/grafana/issues/3655)
* **Metadata**: Settings panel now shows dashboard metadata, closes [#3304](https://github.com/grafana/grafana/issues/3304)
* **InfluxDB**: Support for policy selection in query editor, closes [#2018](https://github.com/grafana/grafana/issues/2018)
* **Snapshots UI**: Dashboard snapshots list can be managed through UI, closes[#1984](https://github.com/grafana/grafana/issues/1984)
* **Prometheus**: Prometheus annotation support, closes[#2883](https://github.com/grafana/grafana/pull/2883)
* **Cli**: New cli tool for downloading and updating plugins
* **Annotations**: Annotations can now contain links that can be clicked (you can navigate on to annotation popovers), closes [#1588](https://github.com/grafana/grafana/issues/1588)
* **Opentsdb**: Opentsdb 2.2 filters support, closes[#3077](https://github.com/grafana/grafana/issues/3077)
### Breaking changes
* **Plugin API**: Both datasource and panel plugin api (and plugin.json schema) have been updated, requiring an update to plugins. See [plugin api](https://github.com/grafana/grafana/blob/master/public/app/plugins/plugin_api.md) for more info.
* **InfluxDB 0.8.x** The data source for the old version of influxdb (0.8.x) is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3523](https://github.com/grafana/grafana/issues/3523)
* **KairosDB** The data source is no longer included in default builds, but can easily be installed via improved plugin system, closes [#3524](https://github.com/grafana/grafana/issues/3524)
* **Templating**: Templating value formats (glob/regex/pipe etc) are now handled automatically and not specified by the user, this makes variable values possible to reuse in many contexts. It can in some edge cases break existing dashboards that have template variables that do not reload on dashboard load. To fix any issue just go into template variable options and update the variable (so it's values are reloaded.).
### Enhancements
* **LDAP**: Support for nested LDAP Groups, closes [#4401](https://github.com/grafana/grafana/issues/4401), [#3808](https://github.com/grafana/grafana/issues/3808)
* **Sessions**: Support for memcached as session storage, closes [#3458](https://github.com/grafana/grafana/issues/3458)
* **mysql**: Grafana now supports ssl for mysql, closes [#3584](https://github.com/grafana/grafana/issues/3584)
* **snapshot**: Annotations are now included in snapshots, closes [#3635](https://github.com/grafana/grafana/issues/3635)
* **Admin**: Admin can now have global overview of Grafana setup, closes [#3812](https://github.com/grafana/grafana/issues/3812)
* **graph**: Right side legend height is now fixed at row height, closes [#1277](https://github.com/grafana/grafana/issues/1277)
* **Table**: All content in table panel is now html escaped, closes [#3673](https://github.com/grafana/grafana/issues/3673)
* **graph**: Template variables can now be used in TimeShift and TimeFrom, closes[#1960](https://github.com/grafana/grafana/issues/1960)
* **Tooltip**: Optionally add milliseconds to timestamp in tool tip, closes[#2248](https://github.com/grafana/grafana/issues/2248)
* **Opentsdb**: Support milliseconds when using openTSDB datasource, closes [#2865](https://github.com/grafana/grafana/issues/2865)
* **Opentsdb**: Add support for annotations, closes[#664](https://github.com/grafana/grafana/issues/664)
### Bug fixes
* **Playlist**: Fix for memory leak when running a playlist, closes [#3794](https://github.com/grafana/grafana/pull/3794)
* **InfluxDB**: Fix for InfluxDB and table panel when using Format As Table and having group by time, fixes [#3928](https://github.com/grafana/grafana/issues/3928)
* **Panel Time shift**: Fix for panel time range and using dashboard times liek `Today` and `This Week`, fixes [#3941](https://github.com/grafana/grafana/issues/3941)
* **Row repeat**: Repeated rows will now appear next to each other and not by the bottom of the dashboard, fixes [#3942](https://github.com/grafana/grafana/issues/3942)
* **Png renderer**: Fix for phantomjs path on windows, fixes [#3657](https://github.com/grafana/grafana/issues/3657)
# 2.6.1 (unrelased, 2.6.x branch)
# 2.6.1
### New Features
* **Elasticsearch**: Support for derivative unit option, closes [#3512](https://github.com/grafana/grafana/issues/3512)
@@ -909,7 +361,7 @@ Grafana 2.x is fundamentally different from 1.x; it now ships with an integrated
# 1.8.0 (2014-09-22)
Read this [blog post](https://grafana.com/blog/2014/09/11/grafana-1.8.0-rc1-released) for an overview of all improvements.
Read this [blog post](http://grafana.org/blog/2014/09/11/grafana-1-8-0-rc1-released.html) for an overview of all improvements.
**Fixes**
- [Issue #802](https://github.com/grafana/grafana/issues/802). Annotations: Fix when using InfluxDB datasource

14
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,14 @@
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!
Prerequisites:
- Nodejs (for jshint & grunt & development server)
Clone repository:
npm install
grunt server (starts development web server in src folder)
grunt (runs jshint and less -> css compilation)
Please remember to run grunt before doing pull request to verify that your code passes all the jshint validations.

171
Godeps/Godeps.json generated Normal file
View File

@@ -0,0 +1,171 @@
{
"ImportPath": "github.com/grafana/grafana",
"GoVersion": "go1.5",
"Packages": [
"./pkg/..."
],
"Deps": [
{
"ImportPath": "github.com/BurntSushi/toml",
"Comment": "v0.1.0-21-g056c9bc",
"Rev": "056c9bc7be7190eaa7715723883caffa5f8fa3e4"
},
{
"ImportPath": "github.com/Unknwon/com",
"Rev": "d9bcf409c8a368d06c9b347705c381e7c12d54df"
},
{
"ImportPath": "github.com/Unknwon/macaron",
"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/cloudwatch",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.0.0",
"Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "2df174808ee097f90d259e432cc04442cf60be21"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v0-48-g060d7da",
"Rev": "060d7da055ba6ec5ea7a31f116332fe5efa04ce0"
},
{
"ImportPath": "github.com/go-ldap/ldap",
"Comment": "v1-19-g83e6542",
"Rev": "83e65426fd1c06626e88aa8a085e5bfed0208e29"
},
{
"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/gosimple/slug",
"Rev": "8d258463b4459f161f51d6a357edacd3eef9d663"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2",
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
},
{
"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/rainycape/unidecode",
"Rev": "836ef0a715aedf08a12d595ed73ec8ed5b288cac"
},
{
"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/asn1-ber.v1",
"Comment": "v1",
"Rev": "9eae18c3681ae3d3c677ac2b80a8fe57de45fc09"
},
{
"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"
}
]
}

5
Godeps/Readme generated Normal file
View 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
View File

@@ -0,0 +1,2 @@
/pkg
/bin

View File

@@ -0,0 +1,24 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
.idea
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.iml

24
Godeps/_workspace/src/github.com/Unknwon/com/README.md generated vendored Normal file
View File

@@ -0,0 +1,24 @@
Common functions
===
[![Build Status](https://drone.io/github.com/Unknwon/com/status.png)](https://drone.io/github.com/Unknwon/com/latest) [![Go Walker](http://gowalker.org/api/v1/badge)](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="."`.

View 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
View 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
}

View 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)
}
})
}

View 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")
}
}

View 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 ------------

View 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")
}
}

View 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))
}
}

View 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()))
}
}

View File

@@ -14,16 +14,11 @@
package com
// PowInt is int type of math.Pow function.
// PowInt is int type of math.Pow function.
func PowInt(x int, y int) int {
if y <= 0 {
return 1
} else {
if y % 2 == 0 {
sqrt := PowInt(x, y/2)
return sqrt * sqrt
} else {
return PowInt(x, y-1) * x
}
num := 1
for i := 0; i < y; i++ {
num *= x
}
return num
}

View File

@@ -64,9 +64,9 @@ func GetSrcPath(importPath string) (appPath string, err error) {
// it returns error when the variable does not exist.
func HomeDir() (home string, err error) {
if runtime.GOOS == "windows" {
home = os.Getenv("USERPROFILE")
if len(home) == 0 {
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
home = os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
if home == "" {
home = os.Getenv("USERPROFILE")
}
} else {
home = os.Getenv("HOME")

View 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()
}
}

View 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")
}
}

View 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
View 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
}

View 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)
}
}

View File

@@ -0,0 +1,2 @@
macaron.sublime-project
macaron.sublime-workspace

View File

@@ -0,0 +1,94 @@
Macaron [![Build Status](https://drone.io/github.com/Unknwon/macaron/status.png)](https://drone.io/github.com/Unknwon/macaron/latest) [![](http://gocover.io/_badge/github.com/Unknwon/macaron)](http://gocover.io/github.com/Unknwon/macaron)
=======================
![Macaron Logo](https://raw.githubusercontent.com/Unknwon/macaron/master/macaronlogo.png)
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)
- [![Join the chat at https://gitter.im/Unknwon/macaron](https://badges.gitter.im/Join%20Chat.svg)](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.

View 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)
}
}

View 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")
})
}

View 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()
}

View 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)
})
}

View File

@@ -0,0 +1,4 @@
inject
======
Dependency injection for go

View 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
}

Some files were not shown because too many files have changed in this diff Show More