Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit b3a2fac

Browse files
committed
Converted REPL script into entry point.
1 parent 98dd2c2 commit b3a2fac

File tree

6 files changed

+324
-127
lines changed

6 files changed

+324
-127
lines changed

examples/shell.py

Lines changed: 0 additions & 126 deletions
This file was deleted.

setup.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,17 @@
5151
url = 'https://github.com/iotaledger/iota.lib.py',
5252
version = '1.1.3',
5353

54+
long_description = long_description,
55+
5456
packages = find_packages('src'),
5557
include_package_data = True,
5658

57-
long_description = long_description,
59+
# http://python-packaging.readthedocs.io/en/latest/command-line-scripts.html#the-console-scripts-entry-point
60+
entry_points = {
61+
'console_scripts': [
62+
'iota-cli=iota.bin.repl:main',
63+
],
64+
},
5865

5966
install_requires = install_dependencies,
6067

src/iota/adapter/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ def __init__(self):
143143

144144
self._logger = None # type: Logger
145145

146+
@abstract_method
147+
def get_uri(self):
148+
# type: () -> Text
149+
"""
150+
Returns the URI that this adapter will use.
151+
"""
152+
raise NotImplementedError(
153+
'Not implemented in {cls}.'.format(cls=type(self).__name__),
154+
)
155+
146156
@abstract_method
147157
def send_request(self, payload, **kwargs):
148158
# type: (dict, dict) -> dict
@@ -248,6 +258,10 @@ def node_url(self):
248258
"""
249259
return self.uri.geturl()
250260

261+
def get_uri(self):
262+
# type: () -> Text
263+
return self.uri.geturl()
264+
251265
def send_request(self, payload, **kwargs):
252266
# type: (dict, dict) -> dict
253267
kwargs.setdefault('headers', {})
@@ -424,6 +438,9 @@ def __init__(self):
424438
self.responses = {} # type: Dict[Text, deque]
425439
self.requests = [] # type: List[dict]
426440

441+
def get_uri(self):
442+
return 'mock://'
443+
427444
def seed_response(self, command, response):
428445
# type: (Text, dict) -> MockAdapter
429446
"""

src/iota/adapter/wrappers.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ def __init__(self, adapter):
2727

2828
self.adapter = adapter # type: BaseAdapter
2929

30+
def get_uri(self):
31+
# type: () -> Text
32+
return self.adapter.get_uri()
33+
3034
@abstract_method
3135
def send_request(self, payload, **kwargs):
3236
# type: (dict, dict) -> dict

src/iota/bin/__init__.py

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# coding=utf-8
2+
from __future__ import absolute_import, division, print_function, \
3+
unicode_literals
4+
5+
import sys
6+
from abc import ABCMeta, abstractmethod as abstract_method
7+
from argparse import ArgumentParser
8+
from getpass import getpass as secure_input
9+
from io import StringIO
10+
from sys import exit
11+
from typing import Optional, Text
12+
13+
from six import text_type, with_metaclass
14+
15+
from iota import Iota, __version__
16+
from iota.crypto.types import Seed
17+
18+
__all__ = [
19+
'IotaCommandLineApp',
20+
]
21+
22+
23+
class IotaCommandLineApp(with_metaclass(ABCMeta)):
24+
"""
25+
Base functionality for a PyOTA-powered command-line application.
26+
"""
27+
requires_seed = True
28+
"""
29+
Whether the command requires the user to provide a seed.
30+
"""
31+
32+
def __init__(self, stdout=sys.stdout, stderr=sys.stderr):
33+
# type: (StringIO, StringIO) -> None
34+
super(IotaCommandLineApp, self).__init__()
35+
36+
self.stdout = stdout
37+
self.stderr = stderr
38+
39+
@abstract_method
40+
def execute(self, api, **arguments):
41+
# type: (Iota, ...) -> Optional[int]
42+
"""
43+
Executes the command and (optionally) returns an exit code (used by
44+
the shell to determine if the application exited cleanly).
45+
46+
:param api:
47+
The API object used to communicate with the node.
48+
49+
:param arguments:
50+
Command-line arguments parsed by the argument parser.
51+
"""
52+
raise NotImplementedError(
53+
'Not implemented in {cls}.'.format(cls=type(self).__name__),
54+
)
55+
56+
def main(self):
57+
"""
58+
Executes the command from :py:data:`sys.argv` and exits.
59+
"""
60+
exit(self.run_from_argv())
61+
62+
def run_from_argv(self, argv=None):
63+
# type: (Optional[tuple]) -> int
64+
"""
65+
Executes the command from a collection of arguments (e.g.,
66+
:py:data`sys.argv`) and returns the exit code.
67+
68+
:param argv:
69+
Arguments to pass to the argument parser.
70+
If ``None``, defaults to ``sys.argv[1:]``.
71+
"""
72+
exit_code = self.execute(**self.parse_argv(argv))
73+
74+
if exit_code is None:
75+
exit_code = 0
76+
77+
return exit_code
78+
79+
def parse_argv(self, argv=None):
80+
# type: (Optional[tuple]) -> dict
81+
"""
82+
Parses arguments for the command.
83+
84+
:param argv:
85+
Arguments to pass to the argument parser.
86+
If ``None``, defaults to ``sys.argv[1:]``.
87+
"""
88+
arguments = vars(self.create_argument_parser().parse_args(argv))
89+
90+
seed = None
91+
if self.requires_seed:
92+
seed_filepath = arguments.pop('seed_file')
93+
94+
seed = (
95+
self.seed_from_filepath(seed_filepath)
96+
if seed_filepath
97+
else self.prompt_for_seed()
98+
)
99+
100+
arguments['api'] =\
101+
Iota(
102+
adapter = arguments.pop('uri'),
103+
seed = seed,
104+
testnet = arguments.pop('testnet'),
105+
)
106+
107+
return arguments
108+
109+
def create_argument_parser(self):
110+
# type: () -> ArgumentParser
111+
"""
112+
Returns the argument parser that will be used to interpret
113+
arguments and options from argv.
114+
"""
115+
parser = ArgumentParser(
116+
description = __doc__,
117+
epilog = 'PyOTA v{version}'.format(version=__version__),
118+
)
119+
120+
parser.add_argument(
121+
'--uri',
122+
type = text_type,
123+
default = 'http://localhost:14265/',
124+
125+
help =
126+
'URI of the node to connect to '
127+
'(defaults to http://localhost:14265/).',
128+
)
129+
130+
if self.requires_seed:
131+
parser.add_argument(
132+
'--seed-file',
133+
type = text_type,
134+
dest = 'seed_file',
135+
136+
help =
137+
'Path to a file containing your seed in cleartext. '
138+
'If not provided, you will be prompted to enter your seed '
139+
'via stdin.',
140+
)
141+
142+
return parser
143+
144+
@staticmethod
145+
def seed_from_filepath(filepath):
146+
# type: (Text) -> Seed
147+
"""
148+
Reads a seed from the first line of a text file.
149+
150+
Any lines after the first are ignored.
151+
"""
152+
with open(filepath, 'rb') as f_:
153+
return Seed(f_.readline().strip())
154+
155+
@staticmethod
156+
def prompt_for_seed():
157+
# type: () -> Seed
158+
"""
159+
Prompts the user to enter their seed via stdin.
160+
"""
161+
seed = secure_input(
162+
'Enter seed and press return (typing will not be shown).\n'
163+
'If no seed is specified, a random one will be used instead.\n'
164+
)
165+
166+
if isinstance(seed, text_type):
167+
seed = seed.encode('ascii')
168+
169+
return Seed(seed) if seed else Seed.random()

0 commit comments

Comments
 (0)