├── index.js ├── docs ├── public │ ├── fonts │ │ ├── aller-bold.eot │ │ ├── aller-bold.ttf │ │ ├── aller-bold.woff │ │ ├── aller-light.eot │ │ ├── aller-light.ttf │ │ ├── aller-light.woff │ │ ├── novecento-bold.eot │ │ ├── novecento-bold.ttf │ │ └── novecento-bold.woff │ └── stylesheets │ │ └── normalize.css ├── docco.css └── discogs.html ├── Makefile ├── package.json ├── test └── discogs.coffee ├── README.md ├── src └── discogs.coffee └── lib └── discogs.js /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./lib/discogs") 2 | -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linus/discogs/HEAD/docs/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PATH := ./node_modules/.bin:${PATH} 2 | 3 | init: 4 | npm install --dev 5 | 6 | test: 7 | nodeunit test 8 | 9 | docs: 10 | docco src/*.coffee 11 | 12 | clean-docs: 13 | rm -rf docs/ 14 | 15 | clean: clean-docs 16 | 17 | dist: clean init docs test 18 | coffee -o lib/ -c src/ 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "discogs", 3 | "description": "Simple client for Discogs API", 4 | "version": "0.4.4", 5 | "homepage": "https://github.com/linus/discogs", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/linus/discogs.git" 9 | }, 10 | "author": "Linus G Thiel (http://hanssonlarsson.se/)", 11 | "contributors": [ 12 | { "name": "Linus G Thiel", "email": "linus@hanssonlarsson.se" }, 13 | { "name": "Adamansky Anton", "url": "https://github.com/adamansky" } 14 | ], 15 | "directories": { 16 | "lib": "lib" 17 | }, 18 | "dependencies": { 19 | "request": ">= 1.2.0" 20 | }, 21 | "devDependencies": { 22 | "coffee-script": ">= 1.1.1", 23 | "docco": ">= 0.3.0", 24 | "nodeunit": "" 25 | }, 26 | "keywords": ["api", "client", "discogs"], 27 | "engines": { "node": "~0.6" } 28 | } 29 | -------------------------------------------------------------------------------- /test/discogs.coffee: -------------------------------------------------------------------------------- 1 | discogs = require '../lib/discogs' 2 | discogs = discogs() 3 | 4 | exports.discogs = 5 | 6 | master: (test) -> 7 | 8 | discogs.master '57279', (err, result) -> 9 | 10 | test.equal result.title, "The Miseducation Of Lauryn Hill" 11 | test.done() 12 | 13 | release: (test) -> 14 | 15 | discogs.release '227020', (err, result) -> 16 | 17 | test.equal result.title, "The Miseducation Of Lauryn Hill" 18 | test.done() 19 | 20 | artist: (test) -> 21 | 22 | discogs.artist 'lauryn hill', (err, result) -> 23 | 24 | test.equal result.realname, 'Lauryn Noel Hill' 25 | test.done() 26 | 27 | label: (test) -> 28 | 29 | discogs.label 'ruffhouse records', (err, result) -> 30 | 31 | test.equal result.name, 'Ruffhouse Records' 32 | test.done() 33 | 34 | search: (test) -> 35 | 36 | discogs.search 'miseducation', (err, result) -> 37 | 38 | result = result.searchresults.results[0] 39 | test.equal result.title, 'Lauryn Hill - The Miseducation Of Lauryn Hill' 40 | test.done() 41 | 42 | lookup: (test) -> 43 | 44 | discogs.lookup 'the miseducation of lauryn hill', (err, result) -> 45 | 46 | test.equal result.title, "The Miseducation Of Lauryn Hill" 47 | test.done() 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Discogs: A simple JavaScript wrapper for the Discogs API 2 | ======================================================== 3 | 4 | `discogs` is a simple wrapper for the [Discogs API](http://www.discogs.com/help/api), written in CoffeeScript and usable in e.g. Node.js. 5 | 6 | ## Version 7 | 0.4.2 8 | 9 | ## Requirements 10 | 11 | - [Node](https://github.com/ry/node) 12 | - [request](https://github.com/mikeal/request) 13 | 14 | ## Installation 15 | 16 | Recommended installation is using [npm](https://github.com/isaacs/npm) 17 | 18 | npm install discogs 19 | 20 | ## Usage 21 | 22 | var discogs = require('discogs'); 23 | 24 | var client = discogs({api_key: 'foo4711'}); 25 | 26 | client.artist('Marcus Price', function(err, artist) { 27 | console.log(artist.name); // Marcus Price 28 | }); 29 | 30 | ## Credits 31 | 32 | Linus G Thiel <linus@hanssonlarsson.se> 33 | [Adamansky Anton](https://github.com/adamansky) - Node 0.6 support 34 | 35 | ## Thank you 36 | 37 | - [Discogs](http://discogs.com/) for an outstanding service and a great API 38 | - [Ryan Dahl](https://github.com/ry) for the awesome Node.js 39 | - [Jeremy Ashkenas](https://github.com/jashkenas) for the beautiful CoffeeScript and Docco 40 | - [Mikeal Rogers](https://github.com/mikeal) for request 41 | 42 | ## License 43 | 44 | (The MIT License) 45 | 46 | Copyright (c) 2010 Hansson & Larsson <info@hanssonlarsson.se> 47 | 48 | Permission is hereby granted, free of charge, to any person obtaining 49 | a copy of this software and associated documentation files (the 50 | 'Software'), to deal in the Software without restriction, including 51 | without limitation the rights to use, copy, modify, merge, publish, 52 | distribute, sublicense, and/or sell copies of the Software, and to 53 | permit persons to whom the Software is furnished to do so, subject to 54 | the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be 57 | included in all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 60 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 61 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 62 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 63 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 64 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 65 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 66 | -------------------------------------------------------------------------------- /src/discogs.coffee: -------------------------------------------------------------------------------- 1 | querystring = require 'querystring' 2 | request = require 'request' 3 | zlib = require 'zlib' 4 | discogs = require '../package.json' 5 | 6 | # This is the entry point. 7 | # 8 | # Takes an optional format parameter, defaulting to 'json'. If 'xml' consumer is expected to take care of parsing 9 | # 10 | # client = discogs("xml") 11 | exports = module.exports = (format) -> 12 | format = null if format is 'json' # JSON is the default and the API doesn't work with f=json 13 | # Return a proper url with optional format 14 | getUrl = (url) -> 15 | 16 | url = "http://api.discogs.com/#{url}" if url.substr(0, 7) isnt 'http://' 17 | sep = if "?" in url then "&" else "?" 18 | 19 | url += "#{sep}f=#{format}" if format 20 | url 21 | 22 | # Make a request 23 | discogsRequest = (url, next) -> 24 | 25 | parseResponse = (err, res, body) -> 26 | 27 | if err then next err 28 | else if ~res.headers['content-type']?.indexOf('json') or not format 29 | try 30 | next null, JSON.parse body 31 | catch e 32 | next e 33 | else 34 | next null, body 35 | 36 | request 37 | uri: getUrl url 38 | headers: 39 | 'accept-encoding': 'gzip' 40 | 'user-agent': "#{discogs.name}/#{discogs.version} +#{discogs.homepage}" 41 | encoding: null 42 | (error, res, body) -> 43 | if not error and 200 <= res.statusCode < 400 44 | if ~res.headers['content-encoding']?.indexOf 'gzip' 45 | zlib.gunzip body, (err, body) -> 46 | parseResponse err, res, body 47 | else 48 | parseResponse error, res, body.toString 'utf8' 49 | else 50 | next error 51 | 52 | responseHandler = (type, next) -> (err, res) -> 53 | 54 | if err then next err 55 | else next null, res?.resp?[type] 56 | 57 | # API 58 | 59 | # Use this if you have a discogs url 60 | get: (url, next) -> 61 | discogsRequest url, next 62 | 63 | # Get a master release 64 | master: (id, next) -> 65 | discogsRequest 'master/' + id, 66 | responseHandler('master', next) 67 | 68 | # Get a release 69 | release: (id, next) -> 70 | discogsRequest 'release/' + id, 71 | responseHandler('release', next) 72 | 73 | # Get an artist 74 | artist: (name, next) -> 75 | discogsRequest 'artist/' + name, 76 | responseHandler('artist', next) 77 | 78 | # Get a label 79 | label: (name, next) -> 80 | discogsRequest 'label/' + name, 81 | responseHandler('label', next) 82 | 83 | # Search for something 84 | # Valid types: 85 | # `all`, `artists`, `labels`, `releases`, `needsvote`, `catno`, `forsale` 86 | search: (query, type, next) -> 87 | if type instanceof Function 88 | next = type 89 | type = 'all' 90 | discogsRequest 'search?' + querystring.stringify(type: type, q: query), 91 | responseHandler('search', next) 92 | 93 | # Do a search and try to find the master or main release in all results. 94 | # Uses 2 or 3 requests per lookup, to find the best result. 95 | lookup: (query, next) -> 96 | @search query, "releases", (err, res) => 97 | return next err if err 98 | 99 | results = res?.searchresults?.results 100 | return next() unless results 101 | 102 | masters = (result for result in results when result.type is "master") 103 | # Did we find masters already? 104 | results = masters if masters.length 105 | 106 | matches = (result for result in results when result.title.toLowerCase() is query.toLowerCase()) 107 | # Take only the best results 108 | results = matches if matches.length 109 | 110 | release = results[0] 111 | id = release.uri.split("/").pop() 112 | 113 | @[release.type] id, (err, res) => 114 | if "master_id" of res 115 | # Did we find a master now? 116 | @master res.master_id, (err, master) => 117 | if "main_release" of master 118 | @release master.main_release, next 119 | else 120 | next null, master 121 | else if "main_release" of res 122 | # Or maybe a main release? 123 | @release res.main_release, next 124 | else 125 | # This is the best we can do 126 | next null, res 127 | -------------------------------------------------------------------------------- /lib/discogs.js: -------------------------------------------------------------------------------- 1 | // Generated by CoffeeScript 1.7.1 2 | (function() { 3 | var discogs, exports, querystring, request, zlib, 4 | __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 5 | 6 | querystring = require('querystring'); 7 | 8 | request = require('request'); 9 | 10 | zlib = require('zlib'); 11 | 12 | discogs = require('../package.json'); 13 | 14 | exports = module.exports = function(format) { 15 | var discogsRequest, getUrl, responseHandler; 16 | if (format === 'json') { 17 | format = null; 18 | } 19 | getUrl = function(url) { 20 | var sep; 21 | if (url.substr(0, 7) !== 'http://') { 22 | url = "http://api.discogs.com/" + url; 23 | } 24 | sep = __indexOf.call(url, "?") >= 0 ? "&" : "?"; 25 | if (format) { 26 | url += "" + sep + "f=" + format; 27 | } 28 | return url; 29 | }; 30 | discogsRequest = function(url, next) { 31 | var parseResponse; 32 | parseResponse = function(err, res, body) { 33 | var e, _ref; 34 | if (err) { 35 | return next(err); 36 | } else if (~((_ref = res.headers['content-type']) != null ? _ref.indexOf('json') : void 0) || !format) { 37 | try { 38 | return next(null, JSON.parse(body)); 39 | } catch (_error) { 40 | e = _error; 41 | return next(e); 42 | } 43 | } else { 44 | return next(null, body); 45 | } 46 | }; 47 | return request({ 48 | uri: getUrl(url), 49 | headers: { 50 | 'accept-encoding': 'gzip', 51 | 'user-agent': "" + discogs.name + "/" + discogs.version + " +" + discogs.homepage 52 | }, 53 | encoding: null 54 | }, function(error, res, body) { 55 | var _ref, _ref1; 56 | if (!error && (200 <= (_ref = res.statusCode) && _ref < 400)) { 57 | if (~((_ref1 = res.headers['content-encoding']) != null ? _ref1.indexOf('gzip') : void 0)) { 58 | return zlib.gunzip(body, function(err, body) { 59 | return parseResponse(err, res, body); 60 | }); 61 | } else { 62 | return parseResponse(error, res, body.toString('utf8')); 63 | } 64 | } else { 65 | return next(error); 66 | } 67 | }); 68 | }; 69 | responseHandler = function(type, next) { 70 | return function(err, res) { 71 | var _ref; 72 | if (err) { 73 | return next(err); 74 | } else { 75 | return next(null, res != null ? (_ref = res.resp) != null ? _ref[type] : void 0 : void 0); 76 | } 77 | }; 78 | }; 79 | return { 80 | get: function(url, next) { 81 | return discogsRequest(url, next); 82 | }, 83 | master: function(id, next) { 84 | return discogsRequest('master/' + id, responseHandler('master', next)); 85 | }, 86 | release: function(id, next) { 87 | return discogsRequest('release/' + id, responseHandler('release', next)); 88 | }, 89 | artist: function(name, next) { 90 | return discogsRequest('artist/' + name, responseHandler('artist', next)); 91 | }, 92 | label: function(name, next) { 93 | return discogsRequest('label/' + name, responseHandler('label', next)); 94 | }, 95 | search: function(query, type, next) { 96 | if (type instanceof Function) { 97 | next = type; 98 | type = 'all'; 99 | } 100 | return discogsRequest('search?' + querystring.stringify({ 101 | type: type, 102 | q: query 103 | }), responseHandler('search', next)); 104 | }, 105 | lookup: function(query, next) { 106 | return this.search(query, "releases", (function(_this) { 107 | return function(err, res) { 108 | var id, masters, matches, release, result, results, _ref; 109 | if (err) { 110 | return next(err); 111 | } 112 | results = res != null ? (_ref = res.searchresults) != null ? _ref.results : void 0 : void 0; 113 | if (!results) { 114 | return next(); 115 | } 116 | masters = (function() { 117 | var _i, _len, _results; 118 | _results = []; 119 | for (_i = 0, _len = results.length; _i < _len; _i++) { 120 | result = results[_i]; 121 | if (result.type === "master") { 122 | _results.push(result); 123 | } 124 | } 125 | return _results; 126 | })(); 127 | if (masters.length) { 128 | results = masters; 129 | } 130 | matches = (function() { 131 | var _i, _len, _results; 132 | _results = []; 133 | for (_i = 0, _len = results.length; _i < _len; _i++) { 134 | result = results[_i]; 135 | if (result.title.toLowerCase() === query.toLowerCase()) { 136 | _results.push(result); 137 | } 138 | } 139 | return _results; 140 | })(); 141 | if (matches.length) { 142 | results = matches; 143 | } 144 | release = results[0]; 145 | id = release.uri.split("/").pop(); 146 | return _this[release.type](id, function(err, res) { 147 | if ("master_id" in res) { 148 | return _this.master(res.master_id, function(err, master) { 149 | if ("main_release" in master) { 150 | return _this.release(master.main_release, next); 151 | } else { 152 | return next(null, master); 153 | } 154 | }); 155 | } else if ("main_release" in res) { 156 | return _this.release(res.main_release, next); 157 | } else { 158 | return next(null, res); 159 | } 160 | }); 161 | }; 162 | })(this)); 163 | } 164 | }; 165 | }; 166 | 167 | }).call(this); 168 | -------------------------------------------------------------------------------- /docs/public/stylesheets/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ 2 | 3 | /* ========================================================================== 4 | HTML5 display definitions 5 | ========================================================================== */ 6 | 7 | /* 8 | * Corrects `block` display not defined in IE 8/9. 9 | */ 10 | 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | hgroup, 19 | nav, 20 | section, 21 | summary { 22 | display: block; 23 | } 24 | 25 | /* 26 | * Corrects `inline-block` display not defined in IE 8/9. 27 | */ 28 | 29 | audio, 30 | canvas, 31 | video { 32 | display: inline-block; 33 | } 34 | 35 | /* 36 | * Prevents modern browsers from displaying `audio` without controls. 37 | * Remove excess height in iOS 5 devices. 38 | */ 39 | 40 | audio:not([controls]) { 41 | display: none; 42 | height: 0; 43 | } 44 | 45 | /* 46 | * Addresses styling for `hidden` attribute not present in IE 8/9. 47 | */ 48 | 49 | [hidden] { 50 | display: none; 51 | } 52 | 53 | /* ========================================================================== 54 | Base 55 | ========================================================================== */ 56 | 57 | /* 58 | * 1. Sets default font family to sans-serif. 59 | * 2. Prevents iOS text size adjust after orientation change, without disabling 60 | * user zoom. 61 | */ 62 | 63 | html { 64 | font-family: sans-serif; /* 1 */ 65 | -webkit-text-size-adjust: 100%; /* 2 */ 66 | -ms-text-size-adjust: 100%; /* 2 */ 67 | } 68 | 69 | /* 70 | * Removes default margin. 71 | */ 72 | 73 | body { 74 | margin: 0; 75 | } 76 | 77 | /* ========================================================================== 78 | Links 79 | ========================================================================== */ 80 | 81 | /* 82 | * Addresses `outline` inconsistency between Chrome and other browsers. 83 | */ 84 | 85 | a:focus { 86 | outline: thin dotted; 87 | } 88 | 89 | /* 90 | * Improves readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* ========================================================================== 99 | Typography 100 | ========================================================================== */ 101 | 102 | /* 103 | * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, 104 | * Safari 5, and Chrome. 105 | */ 106 | 107 | h1 { 108 | font-size: 2em; 109 | } 110 | 111 | /* 112 | * Addresses styling not present in IE 8/9, Safari 5, and Chrome. 113 | */ 114 | 115 | abbr[title] { 116 | border-bottom: 1px dotted; 117 | } 118 | 119 | /* 120 | * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 121 | */ 122 | 123 | b, 124 | strong { 125 | font-weight: bold; 126 | } 127 | 128 | /* 129 | * Addresses styling not present in Safari 5 and Chrome. 130 | */ 131 | 132 | dfn { 133 | font-style: italic; 134 | } 135 | 136 | /* 137 | * Addresses styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | 146 | /* 147 | * Corrects font family set oddly in Safari 5 and Chrome. 148 | */ 149 | 150 | code, 151 | kbd, 152 | pre, 153 | samp { 154 | font-family: monospace, serif; 155 | font-size: 1em; 156 | } 157 | 158 | /* 159 | * Improves readability of pre-formatted text in all browsers. 160 | */ 161 | 162 | pre { 163 | white-space: pre; 164 | white-space: pre-wrap; 165 | word-wrap: break-word; 166 | } 167 | 168 | /* 169 | * Sets consistent quote types. 170 | */ 171 | 172 | q { 173 | quotes: "\201C" "\201D" "\2018" "\2019"; 174 | } 175 | 176 | /* 177 | * Addresses inconsistent and variable font size in all browsers. 178 | */ 179 | 180 | small { 181 | font-size: 80%; 182 | } 183 | 184 | /* 185 | * Prevents `sub` and `sup` affecting `line-height` in all browsers. 186 | */ 187 | 188 | sub, 189 | sup { 190 | font-size: 75%; 191 | line-height: 0; 192 | position: relative; 193 | vertical-align: baseline; 194 | } 195 | 196 | sup { 197 | top: -0.5em; 198 | } 199 | 200 | sub { 201 | bottom: -0.25em; 202 | } 203 | 204 | /* ========================================================================== 205 | Embedded content 206 | ========================================================================== */ 207 | 208 | /* 209 | * Removes border when inside `a` element in IE 8/9. 210 | */ 211 | 212 | img { 213 | border: 0; 214 | } 215 | 216 | /* 217 | * Corrects overflow displayed oddly in IE 9. 218 | */ 219 | 220 | svg:not(:root) { 221 | overflow: hidden; 222 | } 223 | 224 | /* ========================================================================== 225 | Figures 226 | ========================================================================== */ 227 | 228 | /* 229 | * Addresses margin not present in IE 8/9 and Safari 5. 230 | */ 231 | 232 | figure { 233 | margin: 0; 234 | } 235 | 236 | /* ========================================================================== 237 | Forms 238 | ========================================================================== */ 239 | 240 | /* 241 | * Define consistent border, margin, and padding. 242 | */ 243 | 244 | fieldset { 245 | border: 1px solid #c0c0c0; 246 | margin: 0 2px; 247 | padding: 0.35em 0.625em 0.75em; 248 | } 249 | 250 | /* 251 | * 1. Corrects color not being inherited in IE 8/9. 252 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 253 | */ 254 | 255 | legend { 256 | border: 0; /* 1 */ 257 | padding: 0; /* 2 */ 258 | } 259 | 260 | /* 261 | * 1. Corrects font family not being inherited in all browsers. 262 | * 2. Corrects font size not being inherited in all browsers. 263 | * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome 264 | */ 265 | 266 | button, 267 | input, 268 | select, 269 | textarea { 270 | font-family: inherit; /* 1 */ 271 | font-size: 100%; /* 2 */ 272 | margin: 0; /* 3 */ 273 | } 274 | 275 | /* 276 | * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in 277 | * the UA stylesheet. 278 | */ 279 | 280 | button, 281 | input { 282 | line-height: normal; 283 | } 284 | 285 | /* 286 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 287 | * and `video` controls. 288 | * 2. Corrects inability to style clickable `input` types in iOS. 289 | * 3. Improves usability and consistency of cursor style between image-type 290 | * `input` and others. 291 | */ 292 | 293 | button, 294 | html input[type="button"], /* 1 */ 295 | input[type="reset"], 296 | input[type="submit"] { 297 | -webkit-appearance: button; /* 2 */ 298 | cursor: pointer; /* 3 */ 299 | } 300 | 301 | /* 302 | * Re-set default cursor for disabled elements. 303 | */ 304 | 305 | button[disabled], 306 | input[disabled] { 307 | cursor: default; 308 | } 309 | 310 | /* 311 | * 1. Addresses box sizing set to `content-box` in IE 8/9. 312 | * 2. Removes excess padding in IE 8/9. 313 | */ 314 | 315 | input[type="checkbox"], 316 | input[type="radio"] { 317 | box-sizing: border-box; /* 1 */ 318 | padding: 0; /* 2 */ 319 | } 320 | 321 | /* 322 | * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. 323 | * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome 324 | * (include `-moz` to future-proof). 325 | */ 326 | 327 | input[type="search"] { 328 | -webkit-appearance: textfield; /* 1 */ 329 | -moz-box-sizing: content-box; 330 | -webkit-box-sizing: content-box; /* 2 */ 331 | box-sizing: content-box; 332 | } 333 | 334 | /* 335 | * Removes inner padding and search cancel button in Safari 5 and Chrome 336 | * on OS X. 337 | */ 338 | 339 | input[type="search"]::-webkit-search-cancel-button, 340 | input[type="search"]::-webkit-search-decoration { 341 | -webkit-appearance: none; 342 | } 343 | 344 | /* 345 | * Removes inner padding and border in Firefox 4+. 346 | */ 347 | 348 | button::-moz-focus-inner, 349 | input::-moz-focus-inner { 350 | border: 0; 351 | padding: 0; 352 | } 353 | 354 | /* 355 | * 1. Removes default vertical scrollbar in IE 8/9. 356 | * 2. Improves readability and alignment in all browsers. 357 | */ 358 | 359 | textarea { 360 | overflow: auto; /* 1 */ 361 | vertical-align: top; /* 2 */ 362 | } 363 | 364 | /* ========================================================================== 365 | Tables 366 | ========================================================================== */ 367 | 368 | /* 369 | * Remove most spacing between table cells. 370 | */ 371 | 372 | table { 373 | border-collapse: collapse; 374 | border-spacing: 0; 375 | } -------------------------------------------------------------------------------- /docs/docco.css: -------------------------------------------------------------------------------- 1 | /*--------------------- Typography ----------------------------*/ 2 | 3 | @font-face { 4 | font-family: 'aller-light'; 5 | src: url('public/fonts/aller-light.eot'); 6 | src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), 7 | url('public/fonts/aller-light.woff') format('woff'), 8 | url('public/fonts/aller-light.ttf') format('truetype'); 9 | font-weight: normal; 10 | font-style: normal; 11 | } 12 | 13 | @font-face { 14 | font-family: 'aller-bold'; 15 | src: url('public/fonts/aller-bold.eot'); 16 | src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), 17 | url('public/fonts/aller-bold.woff') format('woff'), 18 | url('public/fonts/aller-bold.ttf') format('truetype'); 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'novecento-bold'; 25 | src: url('public/fonts/novecento-bold.eot'); 26 | src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), 27 | url('public/fonts/novecento-bold.woff') format('woff'), 28 | url('public/fonts/novecento-bold.ttf') format('truetype'); 29 | font-weight: normal; 30 | font-style: normal; 31 | } 32 | 33 | /*--------------------- Layout ----------------------------*/ 34 | html { height: 100%; } 35 | body { 36 | font-family: "aller-light"; 37 | font-size: 14px; 38 | line-height: 18px; 39 | color: #30404f; 40 | margin: 0; padding: 0; 41 | height:100%; 42 | } 43 | #container { min-height: 100%; } 44 | 45 | a { 46 | color: #000; 47 | } 48 | 49 | b, strong { 50 | font-weight: normal; 51 | font-family: "aller-bold"; 52 | } 53 | 54 | p { 55 | margin: 15px 0 0px; 56 | } 57 | .annotation ul, .annotation ol { 58 | margin: 25px 0; 59 | } 60 | .annotation ul li, .annotation ol li { 61 | font-size: 14px; 62 | line-height: 18px; 63 | margin: 10px 0; 64 | } 65 | 66 | h1, h2, h3, h4, h5, h6 { 67 | color: #112233; 68 | line-height: 1em; 69 | font-weight: normal; 70 | font-family: "novecento-bold"; 71 | text-transform: uppercase; 72 | margin: 30px 0 15px 0; 73 | } 74 | 75 | h1 { 76 | margin-top: 40px; 77 | } 78 | 79 | hr { 80 | border: 0; 81 | background: 1px #ddd; 82 | height: 1px; 83 | margin: 20px 0; 84 | } 85 | 86 | pre, tt, code { 87 | font-size: 12px; line-height: 16px; 88 | font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; 89 | margin: 0; padding: 0; 90 | } 91 | .annotation pre { 92 | display: block; 93 | margin: 0; 94 | padding: 7px 10px; 95 | background: #fcfcfc; 96 | -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 97 | -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 98 | box-shadow: inset 0 0 10px rgba(0,0,0,0.1); 99 | overflow-x: auto; 100 | } 101 | .annotation pre code { 102 | border: 0; 103 | padding: 0; 104 | background: transparent; 105 | } 106 | 107 | 108 | blockquote { 109 | border-left: 5px solid #ccc; 110 | margin: 0; 111 | padding: 1px 0 1px 1em; 112 | } 113 | .sections blockquote p { 114 | font-family: Menlo, Consolas, Monaco, monospace; 115 | font-size: 12px; line-height: 16px; 116 | color: #999; 117 | margin: 10px 0 0; 118 | white-space: pre-wrap; 119 | } 120 | 121 | ul.sections { 122 | list-style: none; 123 | padding:0 0 5px 0;; 124 | margin:0; 125 | } 126 | 127 | /* 128 | Force border-box so that % widths fit the parent 129 | container without overlap because of margin/padding. 130 | 131 | More Info : http://www.quirksmode.org/css/box.html 132 | */ 133 | ul.sections > li > div { 134 | -moz-box-sizing: border-box; /* firefox */ 135 | -ms-box-sizing: border-box; /* ie */ 136 | -webkit-box-sizing: border-box; /* webkit */ 137 | -khtml-box-sizing: border-box; /* konqueror */ 138 | box-sizing: border-box; /* css3 */ 139 | } 140 | 141 | 142 | /*---------------------- Jump Page -----------------------------*/ 143 | #jump_to, #jump_page { 144 | margin: 0; 145 | background: white; 146 | -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; 147 | -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; 148 | font: 16px Arial; 149 | cursor: pointer; 150 | text-align: right; 151 | list-style: none; 152 | } 153 | 154 | #jump_to a { 155 | text-decoration: none; 156 | } 157 | 158 | #jump_to a.large { 159 | display: none; 160 | } 161 | #jump_to a.small { 162 | font-size: 22px; 163 | font-weight: bold; 164 | color: #676767; 165 | } 166 | 167 | #jump_to, #jump_wrapper { 168 | position: fixed; 169 | right: 0; top: 0; 170 | padding: 10px 15px; 171 | margin:0; 172 | } 173 | 174 | #jump_wrapper { 175 | display: none; 176 | padding:0; 177 | } 178 | 179 | #jump_to:hover #jump_wrapper { 180 | display: block; 181 | } 182 | 183 | #jump_page { 184 | padding: 5px 0 3px; 185 | margin: 0 0 25px 25px; 186 | } 187 | 188 | #jump_page .source { 189 | display: block; 190 | padding: 15px; 191 | text-decoration: none; 192 | border-top: 1px solid #eee; 193 | } 194 | 195 | #jump_page .source:hover { 196 | background: #f5f5ff; 197 | } 198 | 199 | #jump_page .source:first-child { 200 | } 201 | 202 | /*---------------------- Low resolutions (> 320px) ---------------------*/ 203 | @media only screen and (min-width: 320px) { 204 | .pilwrap { display: none; } 205 | 206 | ul.sections > li > div { 207 | display: block; 208 | padding:5px 10px 0 10px; 209 | } 210 | 211 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 212 | padding-left: 30px; 213 | } 214 | 215 | ul.sections > li > div.content { 216 | overflow-x:auto; 217 | -webkit-box-shadow: inset 0 0 5px #e5e5ee; 218 | box-shadow: inset 0 0 5px #e5e5ee; 219 | border: 1px solid #dedede; 220 | margin:5px 10px 5px 10px; 221 | padding-bottom: 5px; 222 | } 223 | 224 | ul.sections > li > div.annotation pre { 225 | margin: 7px 0 7px; 226 | padding-left: 15px; 227 | } 228 | 229 | ul.sections > li > div.annotation p tt, .annotation code { 230 | background: #f8f8ff; 231 | border: 1px solid #dedede; 232 | font-size: 12px; 233 | padding: 0 0.2em; 234 | } 235 | } 236 | 237 | /*---------------------- (> 481px) ---------------------*/ 238 | @media only screen and (min-width: 481px) { 239 | #container { 240 | position: relative; 241 | } 242 | body { 243 | background-color: #F5F5FF; 244 | font-size: 15px; 245 | line-height: 21px; 246 | } 247 | pre, tt, code { 248 | line-height: 18px; 249 | } 250 | p, ul, ol { 251 | margin: 0 0 15px; 252 | } 253 | 254 | 255 | #jump_to { 256 | padding: 5px 10px; 257 | } 258 | #jump_wrapper { 259 | padding: 0; 260 | } 261 | #jump_to, #jump_page { 262 | font: 10px Arial; 263 | text-transform: uppercase; 264 | } 265 | #jump_page .source { 266 | padding: 5px 10px; 267 | } 268 | #jump_to a.large { 269 | display: inline-block; 270 | } 271 | #jump_to a.small { 272 | display: none; 273 | } 274 | 275 | 276 | 277 | #background { 278 | position: absolute; 279 | top: 0; bottom: 0; 280 | width: 350px; 281 | background: #fff; 282 | border-right: 1px solid #e5e5ee; 283 | z-index: -1; 284 | } 285 | 286 | ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { 287 | padding-left: 40px; 288 | } 289 | 290 | ul.sections > li { 291 | white-space: nowrap; 292 | } 293 | 294 | ul.sections > li > div { 295 | display: inline-block; 296 | } 297 | 298 | ul.sections > li > div.annotation { 299 | max-width: 350px; 300 | min-width: 350px; 301 | min-height: 5px; 302 | padding: 13px; 303 | overflow-x: hidden; 304 | white-space: normal; 305 | vertical-align: top; 306 | text-align: left; 307 | } 308 | ul.sections > li > div.annotation pre { 309 | margin: 15px 0 15px; 310 | padding-left: 15px; 311 | } 312 | 313 | ul.sections > li > div.content { 314 | padding: 13px; 315 | vertical-align: top; 316 | border: none; 317 | -webkit-box-shadow: none; 318 | box-shadow: none; 319 | } 320 | 321 | .pilwrap { 322 | position: relative; 323 | display: inline; 324 | } 325 | 326 | .pilcrow { 327 | font: 12px Arial; 328 | text-decoration: none; 329 | color: #454545; 330 | position: absolute; 331 | top: 3px; left: -20px; 332 | padding: 1px 2px; 333 | opacity: 0; 334 | -webkit-transition: opacity 0.2s linear; 335 | } 336 | .for-h1 .pilcrow { 337 | top: 47px; 338 | } 339 | .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { 340 | top: 35px; 341 | } 342 | 343 | ul.sections > li > div.annotation:hover .pilcrow { 344 | opacity: 1; 345 | } 346 | } 347 | 348 | /*---------------------- (> 1025px) ---------------------*/ 349 | @media only screen and (min-width: 1025px) { 350 | 351 | body { 352 | font-size: 16px; 353 | line-height: 24px; 354 | } 355 | 356 | #background { 357 | width: 525px; 358 | } 359 | ul.sections > li > div.annotation { 360 | max-width: 525px; 361 | min-width: 525px; 362 | padding: 10px 25px 1px 50px; 363 | } 364 | ul.sections > li > div.content { 365 | padding: 9px 15px 16px 25px; 366 | } 367 | } 368 | 369 | /*---------------------- Syntax Highlighting -----------------------------*/ 370 | 371 | td.linenos { background-color: #f0f0f0; padding-right: 10px; } 372 | span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } 373 | /* 374 | 375 | github.com style (c) Vasily Polovnyov 376 | 377 | */ 378 | 379 | pre code { 380 | display: block; padding: 0.5em; 381 | color: #000; 382 | background: #f8f8ff 383 | } 384 | 385 | pre .hljs-comment, 386 | pre .hljs-template_comment, 387 | pre .hljs-diff .hljs-header, 388 | pre .hljs-javadoc { 389 | color: #408080; 390 | font-style: italic 391 | } 392 | 393 | pre .hljs-keyword, 394 | pre .hljs-assignment, 395 | pre .hljs-literal, 396 | pre .hljs-css .hljs-rule .hljs-keyword, 397 | pre .hljs-winutils, 398 | pre .hljs-javascript .hljs-title, 399 | pre .hljs-lisp .hljs-title, 400 | pre .hljs-subst { 401 | color: #954121; 402 | /*font-weight: bold*/ 403 | } 404 | 405 | pre .hljs-number, 406 | pre .hljs-hexcolor { 407 | color: #40a070 408 | } 409 | 410 | pre .hljs-string, 411 | pre .hljs-tag .hljs-value, 412 | pre .hljs-phpdoc, 413 | pre .hljs-tex .hljs-formula { 414 | color: #219161; 415 | } 416 | 417 | pre .hljs-title, 418 | pre .hljs-id { 419 | color: #19469D; 420 | } 421 | pre .hljs-params { 422 | color: #00F; 423 | } 424 | 425 | pre .hljs-javascript .hljs-title, 426 | pre .hljs-lisp .hljs-title, 427 | pre .hljs-subst { 428 | font-weight: normal 429 | } 430 | 431 | pre .hljs-class .hljs-title, 432 | pre .hljs-haskell .hljs-label, 433 | pre .hljs-tex .hljs-command { 434 | color: #458; 435 | font-weight: bold 436 | } 437 | 438 | pre .hljs-tag, 439 | pre .hljs-tag .hljs-title, 440 | pre .hljs-rules .hljs-property, 441 | pre .hljs-django .hljs-tag .hljs-keyword { 442 | color: #000080; 443 | font-weight: normal 444 | } 445 | 446 | pre .hljs-attribute, 447 | pre .hljs-variable, 448 | pre .hljs-instancevar, 449 | pre .hljs-lisp .hljs-body { 450 | color: #008080 451 | } 452 | 453 | pre .hljs-regexp { 454 | color: #B68 455 | } 456 | 457 | pre .hljs-class { 458 | color: #458; 459 | font-weight: bold 460 | } 461 | 462 | pre .hljs-symbol, 463 | pre .hljs-ruby .hljs-symbol .hljs-string, 464 | pre .hljs-ruby .hljs-symbol .hljs-keyword, 465 | pre .hljs-ruby .hljs-symbol .hljs-keymethods, 466 | pre .hljs-lisp .hljs-keyword, 467 | pre .hljs-tex .hljs-special, 468 | pre .hljs-input_number { 469 | color: #990073 470 | } 471 | 472 | pre .hljs-builtin, 473 | pre .hljs-constructor, 474 | pre .hljs-built_in, 475 | pre .hljs-lisp .hljs-title { 476 | color: #0086b3 477 | } 478 | 479 | pre .hljs-preprocessor, 480 | pre .hljs-pi, 481 | pre .hljs-doctype, 482 | pre .hljs-shebang, 483 | pre .hljs-cdata { 484 | color: #999; 485 | font-weight: bold 486 | } 487 | 488 | pre .hljs-deletion { 489 | background: #fdd 490 | } 491 | 492 | pre .hljs-addition { 493 | background: #dfd 494 | } 495 | 496 | pre .hljs-diff .hljs-change { 497 | background: #0086b3 498 | } 499 | 500 | pre .hljs-chunk { 501 | color: #aaa 502 | } 503 | 504 | pre .hljs-tex .hljs-formula { 505 | opacity: 0.5; 506 | } 507 | -------------------------------------------------------------------------------- /docs/discogs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | discogs.coffee 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
    15 | 16 |
  • 17 |
    18 |

    discogs.coffee

    19 |
    20 |
  • 21 | 22 | 23 | 24 |
  • 25 |
    26 | 27 |
    28 | 29 |
    30 | 31 |
    32 | 33 |
    querystring = require 'querystring'
     34 | request     = require 'request'
     35 | zlib        = require 'zlib'
     36 | discogs     = require '../package.json'
    37 | 38 |
  • 39 | 40 | 41 |
  • 42 |
    43 | 44 |
    45 | 46 |
    47 |

    This is the entry point.

    48 |

    Takes an optional format parameter, defaulting to ‘json’. If ‘xml’ consumer is expected to take care of parsing

    49 |
    client = discogs("xml")
     50 | 
    51 |
    52 | 53 |
    exports = module.exports = (format) ->
     54 |   format = null if format is 'json' # JSON is the default and the API doesn't work with f=json
    55 | 56 |
  • 57 | 58 | 59 |
  • 60 |
    61 | 62 |
    63 | 64 |
    65 |

    Return a proper url with optional format

    66 | 67 |
    68 | 69 |
      getUrl = (url) ->
     70 | 
     71 |     url = "http://api.discogs.com/#{url}" if url.substr(0, 7) isnt 'http://'
     72 |     sep = if "?" in url then "&" else "?"
     73 | 
     74 |     url += "#{sep}f=#{format}" if format
     75 |     url
    76 | 77 |
  • 78 | 79 | 80 |
  • 81 |
    82 | 83 |
    84 | 85 |
    86 |

    Make a request

    87 | 88 |
    89 | 90 |
      discogsRequest = (url, next) ->
     91 | 
     92 |     parseResponse = (err, res, body) ->
     93 | 
     94 |       if err then next err
     95 |       else if ~res.headers['content-type']?.indexOf('json') or not format
     96 |         try
     97 |           next null, JSON.parse body
     98 |         catch e
     99 |           next e
    100 |       else
    101 |         next null, body
    102 | 
    103 |     request
    104 |       uri: getUrl url
    105 |       headers:
    106 |         'accept-encoding': 'gzip'
    107 |         'user-agent': "#{discogs.name}/#{discogs.version} +#{discogs.homepage}"
    108 |       encoding: null
    109 |       (error, res, body) ->
    110 |         if not error and 200 <= res.statusCode < 400
    111 |           if ~res.headers['content-encoding']?.indexOf 'gzip'
    112 |             zlib.gunzip body, (err, body) ->
    113 |               parseResponse err, res, body
    114 |           else
    115 |             parseResponse error, res, body.toString 'utf8'
    116 |         else
    117 |           next error
    118 | 
    119 |   responseHandler = (type, next) -> (err, res) ->
    120 | 
    121 |       if err then next err
    122 |       else next null, res?.resp?[type]
    123 | 124 |
  • 125 | 126 | 127 |
  • 128 |
    129 | 130 |
    131 | 132 |
    133 |

    API

    134 | 135 |
    136 | 137 |
  • 138 | 139 | 140 |
  • 141 |
    142 | 143 |
    144 | 145 |
    146 |

    Use this if you have a discogs url

    147 | 148 |
    149 | 150 |
      get: (url, next) ->
    151 |     discogsRequest url, next
    152 | 153 |
  • 154 | 155 | 156 |
  • 157 |
    158 | 159 |
    160 | 161 |
    162 |

    Get a master release

    163 | 164 |
    165 | 166 |
      master: (id, next) ->
    167 |     discogsRequest 'master/' + id,
    168 |       responseHandler('master', next)
    169 | 170 |
  • 171 | 172 | 173 |
  • 174 |
    175 | 176 |
    177 | 178 |
    179 |

    Get a release

    180 | 181 |
    182 | 183 |
      release: (id, next) ->
    184 |     discogsRequest 'release/' + id,
    185 |       responseHandler('release', next)
    186 | 187 |
  • 188 | 189 | 190 |
  • 191 |
    192 | 193 |
    194 | 195 |
    196 |

    Get an artist

    197 | 198 |
    199 | 200 |
      artist: (name, next) ->
    201 |     discogsRequest 'artist/' + name,
    202 |       responseHandler('artist', next)
    203 | 204 |
  • 205 | 206 | 207 |
  • 208 |
    209 | 210 |
    211 | 212 |
    213 |

    Get a label

    214 | 215 |
    216 | 217 |
      label: (name, next) ->
    218 |     discogsRequest 'label/' + name,
    219 |       responseHandler('label', next)
    220 | 221 |
  • 222 | 223 | 224 |
  • 225 |
    226 | 227 |
    228 | 229 |
    230 |

    Search for something 231 | Valid types: 232 | all, artists, labels, releases, needsvote, catno, forsale

    233 | 234 |
    235 | 236 |
      search: (query, type, next) ->
    237 |     if type instanceof Function
    238 |       next = type
    239 |       type = 'all'
    240 |     discogsRequest 'search?' + querystring.stringify(type: type, q: query),
    241 |       responseHandler('search', next)
    242 | 243 |
  • 244 | 245 | 246 |
  • 247 |
    248 | 249 |
    250 | 251 |
    252 |

    Do a search and try to find the master or main release in all results. 253 | Uses 2 or 3 requests per lookup, to find the best result.

    254 | 255 |
    256 | 257 |
      lookup: (query, next) ->
    258 |     @search query, "releases", (err, res) =>
    259 |       return next err if err
    260 | 
    261 |       results = res?.searchresults?.results
    262 |       return next() unless results
    263 | 
    264 |       masters = (result for result in results when result.type is "master")
    265 | 266 |
  • 267 | 268 | 269 |
  • 270 |
    271 | 272 |
    273 | 274 |
    275 |

    Did we find masters already?

    276 | 277 |
    278 | 279 |
          results = masters if masters.length
    280 | 
    281 |       matches = (result for result in results when result.title.toLowerCase() is query.toLowerCase())
    282 | 283 |
  • 284 | 285 | 286 |
  • 287 |
    288 | 289 |
    290 | 291 |
    292 |

    Take only the best results

    293 | 294 |
    295 | 296 |
          results = matches if matches.length
    297 | 
    298 |       release = results[0]
    299 |       id = release.uri.split("/").pop()
    300 | 
    301 |       @[release.type] id, (err, res) =>
    302 |         if "master_id" of res
    303 | 304 |
  • 305 | 306 | 307 |
  • 308 |
    309 | 310 |
    311 | 312 |
    313 |

    Did we find a master now?

    314 | 315 |
    316 | 317 |
              @master res.master_id, (err, master) =>
    318 |             if "main_release" of master
    319 |               @release master.main_release, next
    320 |             else
    321 |               next null, master
    322 |         else if "main_release" of res
    323 | 324 |
  • 325 | 326 | 327 |
  • 328 |
    329 | 330 |
    331 | 332 |
    333 |

    Or maybe a main release?

    334 | 335 |
    336 | 337 |
              @release res.main_release, next
    338 |         else
    339 | 340 |
  • 341 | 342 | 343 |
  • 344 |
    345 | 346 |
    347 | 348 |
    349 |

    This is the best we can do

    350 | 351 |
    352 | 353 |
              next null, res
    354 | 355 |
  • 356 | 357 |
358 |
359 | 360 | 361 | --------------------------------------------------------------------------------