├── .gitignore ├── .travis.yml ├── .jshintrc ├── .editorconfig ├── .jscsrc ├── package.json ├── gulpfile.js ├── index.js └── test └── github-linker-resolve_test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | temp/ 3 | coverage/ 4 | npm-debug.log 5 | .DS_Store 6 | .idea 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | after_script: 5 | - npm run coveralls 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "unused": true, 11 | "boss": true, 12 | "eqnull": true, 13 | "node": true, 14 | "globals": { 15 | "describe" : false, 16 | "it" : false, 17 | "before" : false, 18 | "beforeEach" : false, 19 | "after" : false, 20 | "afterEach" : false 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch"], 3 | "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], 4 | "disallowSpaceBeforeBinaryOperators": [",", ":"], 5 | "disallowSpaceAfterBinaryOperators": ["!"], 6 | "requireSpaceBeforeBinaryOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 7 | "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], 8 | "disallowImplicitTypeConversion": ["string"], 9 | "disallowKeywords": ["with"], 10 | "disallowMultipleLineBreaks": true, 11 | "disallowKeywordsOnNewLine": ["else"], 12 | "disallowTrailingWhitespace": true, 13 | "requireLineFeedAtFileEnd": true, 14 | "validateIndentation": 2, 15 | "validateJSDoc": { 16 | "checkParamNames": true, 17 | "requireParamTypes": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-linker", 3 | "description": "Little module that helps with link resolving.", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/MangoTheCat/github-linker", 6 | "bugs": "https://github.com/MangoTheCat/github-linker/issues", 7 | "license": "MIT", 8 | "main": "index.js", 9 | "author": { 10 | "name": "Gabor Csardi", 11 | "email": "gcsardi@mango-solutions.com" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/MangoTheCat/github-linker" 16 | }, 17 | "keywords": [], 18 | "dependencies": { 19 | "duo-parse": "^0.5.0", 20 | "github-url-from-git": "^1.4.0", 21 | "github-url-from-username-repo": "^1.0.2" 22 | }, 23 | "devDependencies": { 24 | "gulp": "^3.9.1", 25 | "gulp-util": "^3.0.7", 26 | "gulp-bump": "^2.1.0", 27 | "gulp-jscs": "^3.0.2", 28 | "gulp-jshint": "^2.0.0", 29 | "gulp-mocha": "^2.2.0", 30 | "gulp-istanbul": "^0.10.4", 31 | "coveralls": "^2.11.9", 32 | "should": "^8.3.0", 33 | "jshint-stylish": "^2.1.0", 34 | "gulp-load-plugins": "^1.2.1", 35 | "gulp-plumber": "^1.1.0" 36 | }, 37 | "scripts": { 38 | "coveralls": "gulp test && cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js", 39 | "test": "gulp test" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var plugins = require('gulp-load-plugins')(); 5 | 6 | var paths = { 7 | lint: ['./gulpfile.js', './index.js'], 8 | watch: ['./gulpfile.js', './index.js', './test/**/*.js', '!test/{temp,temp/**}'], 9 | tests: ['./test/**/*.js', '!test/{temp,temp/**}'], 10 | source: ['./index.js'] 11 | }; 12 | 13 | gulp.task('lint', function () { 14 | return gulp.src(paths.lint) 15 | .pipe(plugins.jshint('.jshintrc')) 16 | .pipe(plugins.plumber()) 17 | .pipe(plugins.jscs()) 18 | .pipe(plugins.jshint.reporter('jshint-stylish')); 19 | }); 20 | 21 | gulp.task('istanbul', function (cb) { 22 | gulp.src(paths.source) 23 | .pipe(plugins.istanbul()) // Covering files 24 | .on('finish', function () { 25 | gulp.src(paths.tests) 26 | .pipe(plugins.plumber()) 27 | .pipe(plugins.mocha()) 28 | .pipe(plugins.istanbul.writeReports()) // Creating the reports after tests runned 29 | .on('finish', function() { 30 | process.chdir(__dirname); 31 | cb(); 32 | }); 33 | }); 34 | }); 35 | 36 | gulp.task('bump', ['test'], function () { 37 | var bumpType = plugins.util.env.type || 'patch'; // major.minor.patch 38 | 39 | return gulp.src(['./package.json']) 40 | .pipe(plugins.bump({ type: bumpType })) 41 | .pipe(gulp.dest('./')); 42 | }); 43 | 44 | gulp.task('watch', ['test'], function () { 45 | gulp.watch(paths.watch, ['test']); 46 | }); 47 | 48 | gulp.task('test', ['lint', 'istanbul']); 49 | 50 | gulp.task('release', ['bump']); 51 | 52 | gulp.task('default', ['test']); 53 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * octo-linker-resolve 3 | * https://github.com/octo-linker/octo-linker-resolve 4 | * 5 | * Copyright (c) 2014 Stefan Buck 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var url = require('url'); 12 | var util = require('util'); 13 | var duoParse = require('duo-parse'); 14 | var ghParse = require('github-url-from-git'); 15 | var ghShorthand = require('github-url-from-username-repo'); 16 | 17 | function isLocalPath(val) { 18 | if (val === '..') { 19 | return true; 20 | } 21 | 22 | var result = val.match(/^(\.\/|\.\.\/)/gm); 23 | if (result && result.length > 0) { 24 | return true; 25 | } 26 | 27 | return false; 28 | } 29 | 30 | function removeTrailingSlash(value) { 31 | if (value.slice(-1) === '/') { 32 | return value.slice(0,-1); 33 | } 34 | 35 | return value; 36 | } 37 | 38 | function localPath (value, href) { 39 | if (isLocalPath(value)) { 40 | return url.resolve(href, value); 41 | } 42 | return null; 43 | } 44 | 45 | function githubShorthand (value) { 46 | return ghShorthand(value, true); 47 | } 48 | 49 | function githubGitUrl (value) { 50 | return ghParse(value); 51 | } 52 | 53 | function duo (value) { 54 | var result = null; 55 | var gh = duoParse(value); 56 | if (gh.user && gh.repo) { 57 | if (gh.path) { 58 | // resolve duojs shorthand like user/repo@master:/file.js 59 | result = util.format('%s/%s/blob/%s%s', gh.user, gh.repo, gh.ref, gh.path); 60 | 61 | } else if (gh.ref) { 62 | // resolve duojs shorthand like user/repo@master 63 | result = util.format('%s/%s/tree/%s', gh.user, gh.repo, gh.ref); 64 | } 65 | 66 | if (result) { 67 | return url.resolve('https://github.com', result); 68 | } 69 | } 70 | return null; 71 | } 72 | 73 | module.exports = function(dep, href) { 74 | var result = ''; 75 | var flow = [localPath, githubShorthand, githubGitUrl, duo]; 76 | 77 | for (var i = flow.length - 1; i >= 0; i--) { 78 | result = flow[i](dep, href); 79 | if (result) { 80 | break; 81 | } 82 | } 83 | 84 | return removeTrailingSlash(result || ''); 85 | }; 86 | -------------------------------------------------------------------------------- /test/github-linker-resolve_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var resolve = require('../'); 4 | var assert = require('should'); 5 | 6 | describe('resolve', function () { 7 | 8 | var baseURL = 'https://github.com/user/repo/'; 9 | 10 | it('packages', function () { 11 | 12 | resolve('path').should.equal(''); 13 | resolve('lodash').should.equal(''); 14 | resolve('unknown-package-name').should.equal(''); 15 | 16 | }); 17 | 18 | describe('normalize', function () { 19 | 20 | describe('./', function () { 21 | 22 | var from = baseURL + 'blob/master/index.js'; 23 | 24 | it('./file.js', function () { 25 | resolve('./file.js', from).should.equal(baseURL + 'blob/master/file.js'); 26 | }); 27 | 28 | it('./folder/file.js', function () { 29 | resolve('./folder/file.js', from).should.equal(baseURL + 'blob/master/folder/file.js'); 30 | }); 31 | 32 | it('./file-or-folder', function () { 33 | resolve('./file-or-folder', from).should.equal(baseURL + 'blob/master/file-or-folder'); 34 | }); 35 | 36 | it('./', function () { 37 | resolve('./', from).should.equal(baseURL + 'blob/master'); 38 | }); 39 | }); 40 | 41 | describe('../', function () { 42 | 43 | var from = baseURL + 'blob/master/a/index.js'; 44 | 45 | it('../file.js', function () { 46 | resolve('../file.js', from).should.equal(baseURL + 'blob/master/file.js'); 47 | }); 48 | 49 | it('../folder/file.js', function () { 50 | resolve('../folder/file.js', from).should.equal(baseURL + 'blob/master/folder/file.js'); 51 | }); 52 | 53 | it('../file-or-folder', function () { 54 | resolve('../file-or-folder', from).should.equal(baseURL + 'blob/master/file-or-folder'); 55 | }); 56 | 57 | it('../', function () { 58 | resolve('../', from).should.equal(baseURL + 'blob/master'); 59 | }); 60 | 61 | it('..', function () { 62 | resolve('..', from).should.equal(baseURL + 'blob/master'); 63 | }); 64 | }); 65 | 66 | describe('../../', function () { 67 | 68 | var from = baseURL + 'blob/master/a/b/index.js'; 69 | 70 | it('../../file.js', function () { 71 | resolve('../../file.js', from).should.equal(baseURL + 'blob/master/file.js'); 72 | }); 73 | 74 | it('../../folder/file.js', function () { 75 | resolve('../../folder/file.js', from).should.equal(baseURL + 'blob/master/folder/file.js'); 76 | }); 77 | 78 | it('../../file-or-folder', function () { 79 | resolve('../../file-or-folder', from).should.equal(baseURL + 'blob/master/file-or-folder'); 80 | }); 81 | 82 | it('../../', function () { 83 | resolve('../../', from).should.equal(baseURL + 'blob/master'); 84 | }); 85 | 86 | it('../..', function () { 87 | resolve('../..', from).should.equal(baseURL + 'blob/master'); 88 | }); 89 | }); 90 | }); 91 | 92 | describe('github shorthand', function () { 93 | 94 | it('user/repo', function () { 95 | resolve('user/repo').should.equal('https://github.com/user/repo'); 96 | }); 97 | 98 | it('user/repo#master', function () { 99 | resolve('user/repo#master').should.equal('https://github.com/user/repo/tree/master'); 100 | }); 101 | }); 102 | 103 | describe('github urls', function () { 104 | 105 | it('jquery/jquery#master', function () { 106 | resolve('jquery/jquery#master').should.equal('https://github.com/jquery/jquery/tree/master'); 107 | }); 108 | 109 | it('jquery/jquery#1.x-master', function () { 110 | resolve('jquery/jquery#1.x-master').should.equal('https://github.com/jquery/jquery/tree/1.x-master'); 111 | }); 112 | 113 | it('git+ssh://github.com:octo-linker/octo-linker-core.git', function () { 114 | resolve('git+ssh://github.com:octo-linker/octo-linker-core.git').should.equal('https://github.com/octo-linker/octo-linker-core'); 115 | }); 116 | 117 | it('git@github.com:octo-linker/octo-linker-core.git', function () { 118 | resolve('git@github.com:octo-linker/octo-linker-core.git').should.equal('https://github.com/octo-linker/octo-linker-core'); 119 | }); 120 | }); 121 | 122 | describe('shorthand duojs', function () { 123 | 124 | it('user/repo@master', function () { 125 | resolve('user/repo@master').should.equal( baseURL + 'tree/master'); 126 | }); 127 | 128 | it('user/repo@dev', function () { 129 | resolve('user/repo@dev').should.equal( baseURL + 'tree/dev'); 130 | }); 131 | 132 | it('user/repo@1.2.3', function () { 133 | resolve('user/repo@1.2.3').should.equal( baseURL + 'tree/1.2.3'); 134 | }); 135 | 136 | it('user/repo@dev/master', function () { 137 | resolve('user/repo@dev/master').should.equal( baseURL + 'tree/dev/master'); 138 | }); 139 | 140 | it('user/repo@dev/master-1.2.3', function () { 141 | resolve('user/repo@dev/master-1.2.3').should.equal( baseURL + 'tree/dev/master-1.2.3'); 142 | }); 143 | 144 | it('user/repo@master:/file.js', function () { 145 | resolve('user/repo@master:/file.js').should.equal( baseURL + 'blob/master/file.js'); 146 | }); 147 | 148 | it('user/repo@master:/folder/file.js', function () { 149 | resolve('user/repo@master:/folder/file.js').should.equal( baseURL + 'blob/master/folder/file.js'); 150 | }); 151 | 152 | it('user/repo@master:/file-or-folder', function () { 153 | resolve('user/repo@master:/file-or-folder').should.equal( baseURL + 'blob/master/file-or-folder'); 154 | }); 155 | }); 156 | 157 | describe('invalid', function () { 158 | 159 | it('.', function () { 160 | resolve('.').should.equal(''); 161 | }); 162 | 163 | it('...', function () { 164 | resolve('...').should.equal(''); 165 | }); 166 | 167 | it('/', function () { 168 | resolve('/').should.equal(''); 169 | }); 170 | 171 | it('@user', function () { 172 | resolve('@user').should.equal(''); 173 | }); 174 | 175 | it('/user', function () { 176 | resolve('/user').should.equal(''); 177 | }); 178 | }); 179 | 180 | }); 181 | --------------------------------------------------------------------------------