Commit 4113773a by Sébastien Eustace

Add a new init command

parent e552d9ab
......@@ -5,6 +5,7 @@
### Added
- Added a new, more efficient dependency resolver.
- Added a new `init` command to generate a `pyproject.toml` file in existing projects.
- Added the `--extras` and `--python` options to `debug:resolve` to help debug dependency resolution.
### Changed
......
......@@ -264,6 +264,24 @@ the `--name` option:
poetry new my-folder --name my-package
```
### init
This command will help you create a `pyproject.toml` file interactively
by prompting you to provide basic information about your package.
It will interactively ask you to fill in the fields, while using some smart defaults.
```bash
poetry init
```
#### Options
* `--name`: Name of the package.
* `--description`: Description of the package.
* `--author`: Author of the package.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--require`.
### install
......
......@@ -54,6 +54,26 @@ the `--name` option:
poetry new my-folder --name my-package
```
## init
This command will help you create a `pyproject.toml` file interactively
by prompting you to provide basic information about your package.
It will interactively ask you to fill in the fields, while using some smart defaults.
```bash
poetry init
```
### Options
* `--name`: Name of the package.
* `--description`: Description of the package.
* `--author`: Author of the package.
* `--dependency`: Package to require with a version constraint. Should be in format `foo:1.0.0`.
* `--dev-dependency`: Development requirements, see `--require`.
## install
The `install` command reads the `pyproject.toml` file from the current project,
......
......@@ -18,6 +18,7 @@ from .commands import AddCommand
from .commands import BuildCommand
from .commands import CheckCommand
from .commands import ConfigCommand
from .commands import InitCommand
from .commands import InstallCommand
from .commands import LockCommand
from .commands import NewCommand
......@@ -106,6 +107,7 @@ class Application(BaseApplication):
BuildCommand(),
CheckCommand(),
ConfigCommand(),
InitCommand(),
InstallCommand(),
LockCommand(),
NewCommand(),
......
......@@ -3,6 +3,7 @@ from .add import AddCommand
from .build import BuildCommand
from .check import CheckCommand
from .config import ConfigCommand
from .init import InitCommand
from .install import InstallCommand
from .lock import LockCommand
from .new import NewCommand
......
import re
from typing import List
from typing import Tuple
from .init import InitCommand
from .venv_command import VenvCommand
class AddCommand(VenvCommand):
class AddCommand(VenvCommand, InitCommand):
"""
Add a new dependency to <comment>pyproject.toml</>.
......@@ -147,94 +143,3 @@ If you do not specify a version constraint, poetry will choose a suitable one ba
self.poetry.file.write(original_content)
return status
def _determine_requirements(self,
requires, # type: List[str]
allow_prereleases=False, # type: bool
): # type: (...) -> List[str]
if not requires:
return []
requires = self._parse_name_version_pairs(requires)
result = []
for requirement in requires:
if 'version' not in requirement:
# determine the best version automatically
name, version = self._find_best_version_for_package(
requirement['name'],
allow_prereleases=allow_prereleases
)
requirement['version'] = version
requirement['name'] = name
self.line(
'Using version <info>{}</> for <info>{}</>'
.format(version, name)
)
else:
# check that the specified version/constraint exists
# before we proceed
name, _ = self._find_best_version_for_package(
requirement['name'], requirement['version'],
allow_prereleases=allow_prereleases
)
requirement['name'] = name
result.append(
'{} {}'.format(requirement['name'], requirement['version'])
)
return result
def _find_best_version_for_package(self,
name,
required_version=None,
allow_prereleases=False
): # type: (...) -> Tuple[str, str]
from poetry.version.version_selector import VersionSelector
selector = VersionSelector(self.poetry.pool)
package = selector.find_best_candidate(
name, required_version,
allow_prereleases=allow_prereleases
)
if not package:
# TODO: find similar
raise ValueError(
'Could not find a matching version of package {}'.format(name)
)
return (
package.pretty_name,
selector.find_recommended_require_version(package)
)
def _parse_name_version_pairs(self, pairs): # type: (list) -> list
result = []
for i in range(len(pairs)):
pair = re.sub('^([^=: ]+)[=: ](.*)$', '\\1 \\2', pairs[i].strip())
pair = pair.strip()
if ' ' in pair:
name, version = pair.split(' ', 2)
result.append({
'name': name,
'version': version
})
else:
result.append({
'name': pair
})
return result
def _format_requirements(self, requirements): # type: (List[str]) -> dict
requires = {}
requirements = self._parse_name_version_pairs(requirements)
for requirement in requirements:
requires[requirement['name']] = requirement['version']
return requires
from poetry.utils._compat import Path
from .command import Command
......@@ -14,6 +12,8 @@ class NewCommand(Command):
def handle(self):
from poetry.layouts import layout
from poetry.utils._compat import Path
from poetry.vcs.git import GitConfig
layout_ = layout('standard')
......@@ -34,7 +34,15 @@ class NewCommand(Command):
readme_format = 'rst'
layout_ = layout_(name, '0.1.0', readme_format=readme_format)
config = GitConfig()
author = None
if config.get('user.name'):
author = config['user.name']
author_email = config.get('user.email')
if author_email:
author += ' <{}>'.format(author_email)
layout_ = layout_(name, '0.1.0', author=author, readme_format=readme_format)
layout_.create(path)
self.line(
......
......@@ -9,7 +9,7 @@ class PoetryStyle(CleoStyle):
self.output.get_formatter().add_style('error', 'red')
self.output.get_formatter().add_style('warning', 'yellow')
self.output.get_formatter().add_style('question', 'blue')
self.output.get_formatter().add_style('question', 'cyan')
self.output.get_formatter().add_style('comment', 'blue')
def writeln(self, messages,
......
from poetry.toml import dumps
from poetry.toml import loads
from poetry.utils.helpers import module_name
from poetry.vcs.git import Git
TESTS_DEFAULT = u"""from {package_name} import __version__
......@@ -20,45 +19,52 @@ description = ""
authors = []
[tool.poetry.dependencies]
python = "*"
[tool.poetry.dev-dependencies]
pytest = "^3.5"
"""
POETRY_WITH_LICENSE = """\
[tool.poetry]
name = ""
version = ""
description = ""
authors = []
license = ""
[tool.poetry.dependencies]
[tool.poetry.dev-dependencies]
"""
class Layout(object):
def __init__(self, project, version='0.1.0', readme_format='md', author=None):
def __init__(self,
project,
version='0.1.0',
description='',
readme_format='md',
author=None,
license=None,
python='*',
dependencies=None,
dev_dependencies=None):
self._project = project
self._package_name = module_name(project)
self._version = version
self._description = description
self._readme_format = readme_format
self._dependencies = {}
self._dev_dependencies = {}
self._include = []
self._license = license
self._python = python
self._dependencies = dependencies or {}
self._dev_dependencies = dev_dependencies or {'pytest': '^3.5'}
self._git = Git()
git_config = self._git.config
if not author:
if (
git_config.get('user.name')
and git_config.get('user.email')
):
author = u'{} <{}>'.format(
git_config['user.name'],
git_config['user.email']
)
else:
author = 'Your Name <you@example.com>'
author = 'Your Name <you@example.com>'
self._author = author
def create(self, path, with_tests=True):
self._dependencies = {}
self._dev_dependencies = {}
self._include = []
path.mkdir(parents=True, exist_ok=True)
self._create_default(path)
......@@ -69,6 +75,30 @@ class Layout(object):
self._write_poetry(path)
def generate_poetry_content(self):
template = POETRY_DEFAULT
if self._license:
template = POETRY_WITH_LICENSE
content = loads(template)
poetry_content = content['tool']['poetry']
poetry_content['name'] = self._project
poetry_content['version'] = self._version
poetry_content['description'] = self._description
poetry_content['authors'].append(self._author)
if self._license:
poetry_content['license'] = self._license
poetry_content['dependencies']['python'] = self._python
for dep_name, dep_constraint in self._dependencies.items():
poetry_content['dependencies'][dep_name] = dep_constraint
for dep_name, dep_constraint in self._dev_dependencies.items():
poetry_content['dev-dependencies'][dep_name] = dep_constraint
return dumps(content)
def _create_default(self, path, src=True):
raise NotImplementedError()
......@@ -99,13 +129,9 @@ class Layout(object):
)
def _write_poetry(self, path):
content = loads(POETRY_DEFAULT)
poetry_content = content['tool']['poetry']
poetry_content['name'] = self._project
poetry_content['version'] = self._version
poetry_content['authors'].append(self._author)
content = self.generate_poetry_content()
poetry = path / 'pyproject.toml'
with poetry.open('w') as f:
f.write(dumps(content))
f.write(content)
......@@ -80,5 +80,14 @@ class Repository(BaseRepository):
if index is not None:
del self._packages[index]
def search(self, query, mode=0):
results = []
for package in self.packages:
if query in package.name:
results.append(package)
return results
def __len__(self):
return len(self._packages)
......@@ -9,17 +9,20 @@ from poetry.utils._compat import decode
class GitConfig:
def __init__(self):
config_list = decode(subprocess.check_output(
['git', 'config', '-l'],
stderr=subprocess.STDOUT
))
self._config = {}
m = re.findall('(?ms)^([^=]+)=(.*?)$', config_list)
if m:
for group in m:
self._config[group[0]] = group[1]
try:
config_list = decode(subprocess.check_output(
['git', 'config', '-l'],
stderr=subprocess.STDOUT
))
m = re.findall('(?ms)^([^=]+)=(.*?)$', config_list)
if m:
for group in m:
self._config[group[0]] = group[1]
except subprocess.CalledProcessError:
pass
def get(self, key, default=None):
return self._config.get(key, default)
......
......@@ -23,7 +23,7 @@ classifiers = [
# Requirements
[tool.poetry.dependencies]
python = "~2.7 || ^3.4"
cleo = "^0.6"
cleo = "^0.6.6"
requests = "^2.18"
toml = "^0.9"
cachy = "^0.2"
......
import pytest
import shutil
import tempfile
from cleo.testers import CommandTester
from poetry.utils._compat import Path
from tests.helpers import get_package
@pytest.fixture
def tmp_dir():
dir_ = tempfile.mkdtemp(prefix='poetry_')
yield dir_
shutil.rmtree(dir_)
def test_basic_interactive(app, mocker):
command = app.find('init')
mocker.patch('poetry.utils._compat.Path.open')
p = mocker.patch('poetry.utils._compat.Path.cwd')
p.return_value = Path(__file__)
tester = CommandTester(command)
tester.set_inputs([
'my-package', # Package name
'1.2.3', # Version
'This is a description', # Description
'n', # Author
'MIT', # License
'~2.7 || ^3.6', # Python
'n', # Interactive packages
'n', # Interactive dev packages
'\n' # Generate
])
tester.execute([('command', command.name)])
output = tester.get_display()
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
[tool.poetry.dev-dependencies]
pytest = "^3.5"
"""
assert expected in output
def test_interactive_with_dependencies(app, repo, mocker):
repo.add_package(get_package('pendulum', '2.0.0'))
command = app.find('init')
mocker.patch('poetry.utils._compat.Path.open')
p = mocker.patch('poetry.utils._compat.Path.cwd')
p.return_value = Path(__file__).parent
tester = CommandTester(command)
tester.set_inputs([
'my-package', # Package name
'1.2.3', # Version
'This is a description', # Description
'n', # Author
'MIT', # License
'~2.7 || ^3.6', # Python
'', # Interactive packages
'pendulum', # Search for package
'0', # First option
'', # Do not set constraint
'', # Stop searching for packages
'n', # Interactive dev packages
'\n' # Generate
])
tester.execute([('command', command.name)])
output = tester.get_display()
expected = """\
[tool.poetry]
name = "my-package"
version = "1.2.3"
description = "This is a description"
authors = ["Your Name <you@example.com>"]
license = "MIT"
[tool.poetry.dependencies]
python = "~2.7 || ^3.6"
pendulum = "^2.0"
[tool.poetry.dev-dependencies]
pytest = "^3.5"
"""
print(output)
assert expected in output
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment