commit
6938ff83d9
@ -0,0 +1 @@ |
||||
dist/ |
@ -0,0 +1,32 @@ |
||||
## Remix URL resolver engine |
||||
|
||||
`resolve(url, urlHandler)` |
||||
|
||||
Returns `json` object with exact same path as `import` statement. |
||||
|
||||
**Output** |
||||
```json |
||||
{ |
||||
content: 'pragma solidity ^0.5.0;\nimport "./mortal.sol";\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) public {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}\n', |
||||
cleanURL: '../greeter.sol', |
||||
type: 'local' |
||||
} |
||||
``` |
||||
|
||||
#### Usage |
||||
|
||||
`resolve(url, urlHandler)` function should be called from within `handleImportCb` function of `solc.compile(input, handleImportCb)`. |
||||
|
||||
```ts |
||||
import { RemixResolve } from 'remix-url-resolver' |
||||
|
||||
const remixResolve = new RemixResolve() |
||||
const fileName: string = '../greeter.sol' |
||||
remixResolve.resolve(fileName, urlHandler) |
||||
.then((sources: object) => { |
||||
console.log(sources) |
||||
}) |
||||
.catch((e: Error) => { |
||||
throw e |
||||
}) |
||||
``` |
@ -0,0 +1,49 @@ |
||||
{ |
||||
"name": "remix-url-resolver", |
||||
"version": "0.0.1", |
||||
"description": "Solidity import url resolver engine", |
||||
"main": "./src/index.js", |
||||
"bin": { |
||||
"remix-url-resolver": "./bin/remix-url-resolver" |
||||
}, |
||||
"scripts": { |
||||
"build": "tsc", |
||||
"lint": "standard", |
||||
"test": "standard && mocha --require ts-node/register tests/*.ts -t 300000" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+https://github.com/ethereum/remix.git" |
||||
}, |
||||
"keywords": [ |
||||
"solidity", |
||||
"remix", |
||||
"resolve", |
||||
"import" |
||||
], |
||||
"author": "Remix Team", |
||||
"license": "MIT", |
||||
"standard": { |
||||
"ignore": [ |
||||
"tests/" |
||||
] |
||||
}, |
||||
"dependencies": { |
||||
"axios": "^0.18.0", |
||||
"solc": "^0.5.0", |
||||
"url": "^0.11.0", |
||||
"valid-url": "^1.0.9" |
||||
}, |
||||
"devDependencies": { |
||||
"@types/chai": "^4.1.7", |
||||
"@types/mocha": "^5.2.5", |
||||
"@types/node": "^10.12.18", |
||||
"chai": "^4.2.0", |
||||
"mocha": "^5.1.0", |
||||
"remix-plugin": "0.0.1-alpha.2", |
||||
"standard": "^12.0.1", |
||||
"ts-node": "^7.0.1", |
||||
"tslint": "^5.11.0", |
||||
"typescript": "^3.1.6" |
||||
} |
||||
} |
@ -0,0 +1 @@ |
||||
export * from './resolve' |
@ -0,0 +1,133 @@ |
||||
import axios, { AxiosResponse } from 'axios' |
||||
|
||||
export interface Imported { |
||||
content: string; |
||||
cleanURL: string; |
||||
type: string; |
||||
} |
||||
|
||||
interface PreviouslyHandledImports { |
||||
[filePath: string]: Imported |
||||
} |
||||
|
||||
interface Handler { |
||||
type: string; |
||||
match(url: string): any; |
||||
handle(match: any): any; |
||||
} |
||||
|
||||
export class RemixURLResolver { |
||||
private previouslyHandled: PreviouslyHandledImports |
||||
constructor() { |
||||
this.previouslyHandled = {} |
||||
} |
||||
/** |
||||
* Handle an import statement based on github |
||||
* @params root The root of the github import statement |
||||
* @params filePath path of the file in github |
||||
*/ |
||||
async handleGithubCall(root: string, filePath: string) { |
||||
try { |
||||
let req: string = 'https://api.github.com/repos/' + root + '/contents/' + filePath |
||||
const response: AxiosResponse = await axios.get(req) |
||||
return Buffer.from(response.data.content, 'base64').toString() |
||||
} catch(e) { |
||||
throw e |
||||
} |
||||
} |
||||
/** |
||||
* Handle an import statement based on http |
||||
* @params url The url of the import statement |
||||
* @params cleanURL |
||||
*/ |
||||
async handleHttp(url: string, _: string) { |
||||
try { |
||||
const response: AxiosResponse = await axios.get(url) |
||||
return response.data |
||||
} catch(e) { |
||||
throw e |
||||
} |
||||
} |
||||
/** |
||||
* Handle an import statement based on https |
||||
* @params url The url of the import statement |
||||
* @params cleanURL |
||||
*/ |
||||
async handleHttps(url: string, _: string) { |
||||
try { |
||||
const response: AxiosResponse = await axios.get(url) |
||||
return response.data |
||||
} catch(e) { |
||||
throw e |
||||
} |
||||
} |
||||
handleSwarm(url: string, cleanURL: string) { |
||||
return |
||||
} |
||||
/** |
||||
* Handle an import statement based on IPFS |
||||
* @params url The url of the IPFS import statement |
||||
*/ |
||||
async handleIPFS(url: string) { |
||||
// replace ipfs:// with /ipfs/
|
||||
url = url.replace(/^ipfs:\/\/?/, 'ipfs/') |
||||
try { |
||||
const req = 'https://gateway.ipfs.io/' + url |
||||
// If you don't find greeter.sol on ipfs gateway use local
|
||||
// const req = 'http://localhost:8080/' + url
|
||||
const response: AxiosResponse = await axios.get(req) |
||||
return response.data |
||||
} catch (e) { |
||||
throw e |
||||
} |
||||
} |
||||
getHandlers(): Handler[] { |
||||
return [ |
||||
{ |
||||
type: 'github', |
||||
match: (url) => { return /^(https?:\/\/)?(www.)?github.com\/([^/]*\/[^/]*)\/(.*)/.exec(url) }, |
||||
handle: (match) => this.handleGithubCall(match[3], match[4]) |
||||
}, |
||||
{ |
||||
type: 'http', |
||||
match: (url) => { return /^(http?:\/\/?(.*))$/.exec(url) }, |
||||
handle: (match) => this.handleHttp(match[1], match[2]) |
||||
}, |
||||
{ |
||||
type: 'https', |
||||
match: (url) => { return /^(https?:\/\/?(.*))$/.exec(url) }, |
||||
handle: (match) => this.handleHttps(match[1], match[2]) |
||||
}, |
||||
{ |
||||
type: 'swarm', |
||||
match: (url) => { return /^(bzz-raw?:\/\/?(.*))$/.exec(url) }, |
||||
handle: (match) => this.handleSwarm(match[1], match[2]) |
||||
}, |
||||
{ |
||||
type: 'ipfs', |
||||
match: (url) => { return /^(ipfs:\/\/?.+)/.exec(url) }, |
||||
handle: (match) => this.handleIPFS(match[1]) |
||||
} |
||||
] |
||||
} |
||||
|
||||
public async resolve(filePath: string, customHandlers?: Handler[]): Promise<Imported> { |
||||
var imported: Imported = this.previouslyHandled[filePath] |
||||
if(imported) { |
||||
return imported |
||||
} |
||||
const builtinHandlers: Handler[] = this.getHandlers() |
||||
const handlers: Handler[] = customHandlers ? [...builtinHandlers, ...customHandlers] : [...builtinHandlers] |
||||
const matchedHandler = handlers.filter(handler => handler.match(filePath)) |
||||
const handler: Handler = matchedHandler[0] |
||||
const match = handler.match(filePath) |
||||
const content: string = await handler.handle(match) |
||||
imported = { |
||||
content, |
||||
cleanURL: filePath, |
||||
type: handler.type |
||||
} |
||||
this.previouslyHandled[filePath] = imported |
||||
return imported |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
pragma solidity ^0.5.0; |
||||
import "./mortal.sol"; |
||||
|
||||
contract Greeter is Mortal { |
||||
/* Define variable greeting of the type string */ |
||||
string greeting; |
||||
|
||||
/* This runs when the contract is executed */ |
||||
constructor(string memory _greeting) public { |
||||
greeting = _greeting; |
||||
} |
||||
|
||||
/* Main function */ |
||||
function greet() public view returns (string memory) { |
||||
return greeting; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
contract Mortal { |
||||
/* Define variable owner of the type address */ |
||||
address payable owner; |
||||
|
||||
/* This function is executed at initialization and sets the owner of the contract */ |
||||
function mortal() public { owner = msg.sender; } |
||||
|
||||
/* Function to recover the funds on the contract */ |
||||
function kill() public { if (msg.sender == owner) selfdestruct(owner); } |
||||
} |
@ -0,0 +1,146 @@ |
||||
import { RemixURLResolver } from '../src' |
||||
import * as fs from 'fs' |
||||
import * as path from 'path' |
||||
import * as assert from 'assert' |
||||
|
||||
describe('testRunner', () => { |
||||
describe('# RemixResolve.resolve()', () => { |
||||
describe('* test without AppManager', () => { |
||||
describe('test example_1 [local imports]', () => { |
||||
const remixURLResolve = new RemixURLResolver() |
||||
const fileName: string = '../remix-url-resolver/tests/example_1/greeter.sol' |
||||
let results: object = {} |
||||
|
||||
before(done => { |
||||
function handleLocal(pathString: string, filePath: string) { |
||||
// if no relative/absolute path given then search in node_modules folder
|
||||
if (pathString && pathString.indexOf('.') !== 0 && pathString.indexOf('/') !== 0) { |
||||
// return handleNodeModulesImport(pathString, filePath, pathString)
|
||||
return |
||||
} else { |
||||
const o = { encoding: 'UTF-8' } |
||||
const p = pathString ? path.resolve(pathString, filePath) : path.resolve(pathString, filePath) |
||||
const content = fs.readFileSync(p, o) |
||||
return content |
||||
} |
||||
} |
||||
const localFSHandler = [ |
||||
{ |
||||
type: 'local', |
||||
match: (url: string) => { return /(^(?!(?:http:\/\/)|(?:https:\/\/)?(?:www.)?(?:github.com)))(^\/*[\w+-_/]*\/)*?(\w+\.sol)/g.exec(url) }, |
||||
handle: (match: Array<string>) => { return handleLocal(match[2], match[3]) } |
||||
} |
||||
] |
||||
remixURLResolve.resolve(fileName, localFSHandler) |
||||
.then((sources: object) => { |
||||
results = sources |
||||
done() |
||||
}) |
||||
.catch((e: Error) => { |
||||
throw e |
||||
}) |
||||
}) |
||||
|
||||
it('should have 3 items', () => { |
||||
assert.equal(Object.keys(results).length, 3) |
||||
}) |
||||
it('should return contract content of given local path', () => { |
||||
const expt = { |
||||
content: 'pragma solidity ^0.5.0;\nimport "./mortal.sol";\n\ncontract Greeter is Mortal {\n /* Define variable greeting of the type string */\n string greeting;\n\n /* This runs when the contract is executed */\n constructor(string memory _greeting) public {\n greeting = _greeting;\n }\n\n /* Main function */\n function greet() public view returns (string memory) {\n return greeting;\n }\n}\n', |
||||
cleanURL: '../remix-url-resolver/tests/example_1/greeter.sol', |
||||
type: 'local' |
||||
} |
||||
assert.deepEqual(results, expt) |
||||
}) |
||||
}) |
||||
// Test github import
|
||||
describe('test getting github imports', () => { |
||||
const remixURLResolve = new RemixURLResolver() |
||||
const fileName: string = 'github.com/ethereum/populus/docs/assets/Greeter.sol' |
||||
let results: object = {} |
||||
|
||||
before(done => { |
||||
remixURLResolve.resolve(fileName) |
||||
.then((sources: object) => { |
||||
results = sources |
||||
done() |
||||
}) |
||||
.catch((e: Error) => { |
||||
throw e |
||||
}) |
||||
}) |
||||
|
||||
it('should have 3 items', () => { |
||||
assert.equal(Object.keys(results).length, 3) |
||||
}) |
||||
it('should return contract content of given github path', () => { |
||||
const expt: object = { |
||||
cleanURL: 'github.com/ethereum/populus/docs/assets/Greeter.sol', |
||||
content: 'pragma solidity ^0.4.0;\n\ncontract Greeter {\n string public greeting;\n\n // TODO: Populus seems to get no bytecode if `internal`\n function Greeter() public {\n greeting = \'Hello\';\n }\n\n function setGreeting(string _greeting) public {\n greeting = _greeting;\n }\n\n function greet() public constant returns (string) {\n return greeting;\n }\n}\n', |
||||
type: 'github' |
||||
} |
||||
assert.deepEqual(results, expt) |
||||
}) |
||||
}) |
||||
// Test https imports
|
||||
describe('test getting https imports', () => { |
||||
const remixURLResolve = new RemixURLResolver() |
||||
const fileName: string = 'https://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol' |
||||
let results: object = {} |
||||
|
||||
before(done => { |
||||
remixURLResolve.resolve(fileName) |
||||
.then((sources: object) => { |
||||
results = sources |
||||
done() |
||||
}) |
||||
.catch((e: Error) => { |
||||
throw e |
||||
}) |
||||
}) |
||||
|
||||
it('should have 3 items', () => { |
||||
assert.equal(Object.keys(results).length, 3) |
||||
}) |
||||
it('should return contract content from raw github url', () => { |
||||
const expt: object = { |
||||
content: 'contract mortal {\n /* Define variable owner of the type address*/\n address owner;\n\n /* this function is executed at initialization and sets the owner of the contract */\n function mortal() { owner = msg.sender; }\n\n /* Function to recover the funds on the contract */\n function kill() { if (msg.sender == owner) suicide(owner); }\n}\n\ncontract greeter is mortal {\n /* define variable greeting of the type string */\n string greeting;\n\n /* this runs when the contract is executed */\n function greeter(string _greeting) public {\n greeting = _greeting;\n }\n\n /* main function */\n function greet() constant returns (string) {\n return greeting;\n }\n}', |
||||
cleanURL: 'https://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol', |
||||
type: 'https' |
||||
} |
||||
assert.deepEqual(results, expt) |
||||
}) |
||||
}) |
||||
|
||||
// Test http imports
|
||||
describe('test getting http imports', () => { |
||||
const remixURLResolve = new RemixURLResolver() |
||||
const fileName: string = 'http://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol' |
||||
let results: object = {} |
||||
|
||||
before(done => { |
||||
remixURLResolve.resolve(fileName) |
||||
.then((sources: object) => { |
||||
results = sources |
||||
done() |
||||
}) |
||||
.catch((e: Error) => { |
||||
throw e |
||||
}) |
||||
}) |
||||
|
||||
it('should have 3 items', () => { |
||||
assert.equal(Object.keys(results).length, 3) |
||||
}) |
||||
it('should return contract content from raw github url', () => { |
||||
const expt: object = { |
||||
content: 'contract mortal {\n /* Define variable owner of the type address*/\n address owner;\n\n /* this function is executed at initialization and sets the owner of the contract */\n function mortal() { owner = msg.sender; }\n\n /* Function to recover the funds on the contract */\n function kill() { if (msg.sender == owner) suicide(owner); }\n}\n\ncontract greeter is mortal {\n /* define variable greeting of the type string */\n string greeting;\n\n /* this runs when the contract is executed */\n function greeter(string _greeting) public {\n greeting = _greeting;\n }\n\n /* main function */\n function greet() constant returns (string) {\n return greeting;\n }\n}', |
||||
cleanURL: 'http://gist.githubusercontent.com/roneilr/7901633d7c2f52957d22/raw/d9b9d54760f6e4f4cfbac4b321bee6a6983a1048/greeter.sol', |
||||
type: 'http' |
||||
} |
||||
assert.deepEqual(results, expt) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
||||
}) |
@ -0,0 +1,21 @@ |
||||
{ |
||||
"compileOnSave": false, |
||||
"include": ["./src"], |
||||
"compilerOptions": { |
||||
"baseUrl": "./src", |
||||
"outDir": "./dist", |
||||
"sourceMap": true, |
||||
"declaration": false, |
||||
"module": "commonjs", |
||||
"strict": true, |
||||
"noImplicitAny": false, |
||||
"strictPropertyInitialization": false, |
||||
"experimentalDecorators": true, |
||||
"target": "es5", |
||||
"typeRoots": ["node_modules/@types"], |
||||
"lib": ["dom", "es2018"], |
||||
"paths": { |
||||
"remix-url-resolver": ["./"] |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,113 @@ |
||||
{ |
||||
"rules": { |
||||
"arrow-return-shorthand": true, |
||||
"callable-types": true, |
||||
"class-name": true, |
||||
"comment-format": [ |
||||
true, |
||||
"check-space" |
||||
], |
||||
"curly": false, |
||||
"deprecation": { |
||||
"severity": "warn" |
||||
}, |
||||
"forin": false, |
||||
"import-spacing": true, |
||||
"indent": [ |
||||
true, |
||||
"spaces" |
||||
], |
||||
"interface-over-type-literal": true, |
||||
"label-position": true, |
||||
"max-line-length": [ |
||||
true, |
||||
140 |
||||
], |
||||
"member-access": false, |
||||
"member-ordering": [ |
||||
true, |
||||
{ |
||||
"order": [ |
||||
"static-field", |
||||
"instance-field", |
||||
"static-method", |
||||
"instance-method" |
||||
] |
||||
} |
||||
], |
||||
"no-arg": true, |
||||
"no-bitwise": false, |
||||
"no-console": [ |
||||
true, |
||||
"debug", |
||||
"info", |
||||
"time", |
||||
"timeEnd", |
||||
"trace" |
||||
], |
||||
"no-construct": true, |
||||
"no-duplicate-super": true, |
||||
"no-empty": false, |
||||
"no-empty-interface": true, |
||||
"no-eval": true, |
||||
"no-inferrable-types": [ |
||||
true, |
||||
"ignore-params" |
||||
], |
||||
"no-misused-new": true, |
||||
"no-non-null-assertion": true, |
||||
"no-redundant-jsdoc": true, |
||||
"no-shadowed-variable": true, |
||||
"no-string-literal": false, |
||||
"no-string-throw": true, |
||||
"no-switch-case-fall-through": true, |
||||
"no-trailing-whitespace": true, |
||||
"no-unnecessary-initializer": true, |
||||
"no-unused-expression": true, |
||||
"no-use-before-declare": true, |
||||
"no-var-keyword": true, |
||||
"object-literal-sort-keys": false, |
||||
"one-line": [ |
||||
true, |
||||
"check-open-brace", |
||||
"check-catch", |
||||
"check-else", |
||||
"check-whitespace" |
||||
], |
||||
"prefer-const": true, |
||||
"quotemark": [ |
||||
false, |
||||
"single" |
||||
], |
||||
"radix": true, |
||||
"semicolon": [ |
||||
true, |
||||
"never" |
||||
], |
||||
"triple-equals": [ |
||||
true, |
||||
"allow-null-check" |
||||
], |
||||
"typedef-whitespace": [ |
||||
true, |
||||
{ |
||||
"call-signature": "nospace", |
||||
"index-signature": "nospace", |
||||
"parameter": "nospace", |
||||
"property-declaration": "nospace", |
||||
"variable-declaration": "nospace" |
||||
} |
||||
], |
||||
"unified-signatures": true, |
||||
"variable-name": false, |
||||
"whitespace": [ |
||||
true, |
||||
"check-branch", |
||||
"check-decl", |
||||
"check-operator", |
||||
"check-separator", |
||||
"check-type" |
||||
] |
||||
} |
||||
} |
||||
|
Loading…
Reference in new issue