Skip to content

Commit

Permalink
Merge pull request #1 from pipermerriam/piper/initial-implementation
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
pipermerriam authored Jul 19, 2016
2 parents 328cc4f + c340724 commit 5d9437a
Show file tree
Hide file tree
Showing 19 changed files with 623 additions and 6 deletions.
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,9 @@ sudo: required
before_install:
- travis_retry sudo add-apt-repository -y ppa:ethereum/ethereum
- travis_retry sudo apt-get update
- travis_retry sudo apt-get install -y ethereum
- mkdir -p ~/.ethash
- geth makedag 0 ~/.ethash
- travis_retry sudo apt-get install -y solc
cache:
pip: true
directories:
- ~/.ethash
env:
matrix:
- TOX_ENV=py27
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pip install py-solc
},
},
}
>>> compile_source(["/path/to/Foo.sol", "/path/to/Bar.sol"])
>>> compile_files(["/path/to/Foo.sol", "/path/to/Bar.sol"])
{
'Foo': {
'abi': [{'inputs': [], 'type': 'constructor'}],
Expand Down
6 changes: 6 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import pytest


@pytest.fixture()
def contracts_dir(tmpdir):
return str(tmpdir.mkdir("contracts"))
2 changes: 2 additions & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pytest>=2.9.2
tox>=2.3.1
Empty file added requirements.txt
Empty file.
7 changes: 7 additions & 0 deletions solc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from __future__ import absolute_import

from .main import ( # NOQA
get_solc_version,
compile_files,
compile_source,
)
6 changes: 6 additions & 0 deletions solc/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class SolcError(Exception):
pass


class CompileError(Exception):
pass
123 changes: 123 additions & 0 deletions solc/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from __future__ import absolute_import

import functools
import json
import re

from .exceptions import (
SolcError,
)

from .utils.formatting import (
add_0x_prefix,
)
from .utils.filesystem import (
is_executable_available,
)
from .wrapper import (
SOLC_BINARY,
solc_wrapper,
)


version_regex = re.compile('Version: ([0-9]+\.[0-9]+\.[0-9]+(-[a-f0-9]+)?)')


is_solc_available = functools.partial(is_executable_available, SOLC_BINARY)


def get_solc_version():
stdoutdata, stderrdata = solc_wrapper(version=True)
version_match = version_regex.search(stdoutdata)
if version_match is None:
raise SolcError(
"Unable to extract version string from command output: `{0}`".format(
stdoutdata,
)
)
return version_match.groups()[0]


def _parse_compiler_output(stdoutdata):
contracts = json.loads(stdoutdata)['contracts']

for _, data in contracts.items():
data['abi'] = json.loads(data['abi'])

sorted_contracts = sorted(contracts.items(), key=lambda c: c[0])

compiler_version = get_solc_version()

return {
contract_name: {
'abi': contract_data['abi'],
'code': add_0x_prefix(contract_data['bin']),
'code_runtime': add_0x_prefix(contract_data['bin-runtime']),
'source': None,
'meta': {
'compilerVersion': compiler_version,
'language': 'Solidity',
'languageVersion': '0',
},
}
for contract_name, contract_data
in sorted_contracts
}


ALL_OUTPUT_VALUES = [
"abi",
"asm",
"ast",
"bin",
"bin-runtime",
"clone-bin",
"devdoc",
"interface",
"opcodes",
"userdoc",
]


def compile_source(source, output_values=ALL_OUTPUT_VALUES, **kwargs):
if 'stdin_bytes' in kwargs:
raise ValueError(
"The `stdin_bytes` keyword is not allowed in the `compile_source` function"
)
if 'combined_json' in kwargs:
raise ValueError(
"The `combined_json` keyword is not allowed in the `compile_source` function"
)

combined_json = ','.join(output_values)

stdoutdata, stderrdata = solc_wrapper(
stdin_bytes=source,
combined_json=combined_json,
**kwargs
)

contracts = _parse_compiler_output(stdoutdata)
return contracts


def compile_files(source_files, output_values=ALL_OUTPUT_VALUES, **kwargs):
if 'source_files' in kwargs:
raise ValueError(
"The `source_files` keyword is not allowed in the `compile_files` function"
)
if 'combined_json' in kwargs:
raise ValueError(
"The `combined_json` keyword is not allowed in the `compile_files` function"
)

combined_json = ','.join(output_values)

stdoutdata, stderrdata = solc_wrapper(
source_files=source_files,
combined_json=combined_json,
**kwargs
)

contracts = _parse_compiler_output(stdoutdata)
return contracts
Empty file added solc/utils/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions solc/utils/filesystem.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import os


def is_executable_available(program):
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

fpath = os.path.dirname(program)
if fpath:
if is_exe(program):
return True
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return True

return False
33 changes: 33 additions & 0 deletions solc/utils/formatting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from __future__ import absolute_import

from .string import (
force_bytes,
force_text,
)
from .types import (
is_bytes,
)


def is_prefixed(value, prefix):
return value.startswith(
force_bytes(prefix) if is_bytes(value) else force_text(prefix)
)


def is_0x_prefixed(value):
return is_prefixed(value, '0x')


def remove_0x_prefix(value):
if is_0x_prefixed(value):
return value[2:]
return value


def add_0x_prefix(value):
if is_0x_prefixed(value):
return value

prefix = b'0x' if is_bytes(value) else '0x'
return prefix + value
92 changes: 92 additions & 0 deletions solc/utils/string.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import absolute_import

import sys
import functools

from .types import (
is_bytes,
is_text,
is_string,
)

if sys.version_info.major == 2:
def force_bytes(value):
if is_bytes(value):
return str(value)
elif is_text(value):
return value.encode('latin1')
else:
raise TypeError("Unsupported type: {0}".format(type(value)))

def force_text(value):
if is_text(value):
return value
elif is_bytes(value):
return unicode(force_bytes(value), 'latin1') # NOQA
else:
raise TypeError("Unsupported type: {0}".format(type(value)))
else:
def force_bytes(value):
if is_bytes(value):
return bytes(value)
elif is_text(value):
return bytes(value, 'latin1')
else:
raise TypeError("Unsupported type: {0}".format(type(value)))

def force_text(value):
if is_text(value):
return value
elif is_bytes(value):
return str(value, 'latin1')
else:
raise TypeError("Unsupported type: {0}".format(type(value)))


def force_obj_to_bytes(obj):
if is_string(obj):
return force_bytes(obj)
elif isinstance(obj, dict):
return {
k: force_obj_to_bytes(v) for k, v in obj.items()
}
elif isinstance(obj, (list, tuple)):
return type(obj)(force_obj_to_bytes(v) for v in obj)
else:
return obj


def force_obj_to_text(obj):
if is_string(obj):
return force_text(obj)
elif isinstance(obj, dict):
return {
k: force_obj_to_text(v) for k, v in obj.items()
}
elif isinstance(obj, (list, tuple)):
return type(obj)(force_obj_to_text(v) for v in obj)
else:
return obj


def coerce_args_to_bytes(fn):
@functools.wraps(fn)
def inner(*args, **kwargs):
bytes_args = force_obj_to_bytes(args)
bytes_kwargs = force_obj_to_bytes(kwargs)
return fn(*bytes_args, **bytes_kwargs)
return inner


def coerce_return_to_bytes(fn):
@functools.wraps(fn)
def inner(*args, **kwargs):
return force_obj_to_bytes(fn(*args, **kwargs))
return inner


def coerce_return_to_text(fn):
@functools.wraps(fn)
def inner(*args, **kwargs):
return force_obj_to_text(fn(*args, **kwargs))
return inner
41 changes: 41 additions & 0 deletions solc/utils/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import sys


if sys.version_info.major == 2:
integer_types = (int, long) # NOQA
bytes_types = (bytes, bytearray)
text_types = (unicode,) # NOQA
string_types = (basestring, bytearray) # NOQA
else:
integer_types = (int,)
bytes_types = (bytes, bytearray)
text_types = (str,)
string_types = (bytes, str, bytearray)


def is_integer(value):
return isinstance(value, integer_types) and not isinstance(value, bool)


def is_bytes(value):
return isinstance(value, bytes_types)


def is_text(value):
return isinstance(value, text_types)


def is_string(value):
return isinstance(value, string_types)


def is_boolean(value):
return isinstance(value, bool)


def is_object(obj):
return isinstance(obj, dict)


def is_array(obj):
return isinstance(obj, list)
Loading

0 comments on commit 5d9437a

Please sign in to comment.