diff --git a/docs/topics/commands.rst b/docs/topics/commands.rst index d7999900b..32669104c 100644 --- a/docs/topics/commands.rst +++ b/docs/topics/commands.rst @@ -103,13 +103,14 @@ Creating projects The first thing you typically do with the ``scrapy`` tool is create your Scrapy project:: - scrapy startproject myproject + scrapy startproject myproject [project_dir] -That will create a Scrapy project under the ``myproject`` directory. +That will create a Scrapy project under the ``project_dir`` directory. +If ``project_dir`` wasn't specified, ``project_dir`` will be the same as ``myproject``. Next, you go inside the new project directory:: - cd myproject + cd project_dir And you're ready to use the ``scrapy`` command to manage and control your project from there. @@ -181,11 +182,12 @@ Project-only commands: startproject ------------ -* Syntax: ``scrapy startproject `` +* Syntax: ``scrapy startproject [project_dir]`` * Requires project: *no* -Creates a new Scrapy project named ``project_name``, under the ``project_name`` +Creates a new Scrapy project named ``project_name``, under the ``project_dir`` directory. +If ``project_dir`` wasn't specified, ``project_dir`` will be the same as ``myproject``. Usage example:: diff --git a/scrapy/commands/startproject.py b/scrapy/commands/startproject.py index b3c5011df..e3989baaf 100644 --- a/scrapy/commands/startproject.py +++ b/scrapy/commands/startproject.py @@ -1,9 +1,10 @@ from __future__ import print_function import re +import os import string from importlib import import_module from os.path import join, exists, abspath -from shutil import copytree, ignore_patterns, move +from shutil import ignore_patterns, move, copy2, copystat import scrapy from scrapy.commands import ScrapyCommand @@ -27,7 +28,7 @@ class Command(ScrapyCommand): default_settings = {'LOG_ENABLED': False} def syntax(self): - return "" + return " [project_dir]" def short_desc(self): return "Create new project" @@ -43,36 +44,72 @@ class Command(ScrapyCommand): if not re.search(r'^[_a-zA-Z]\w*$', project_name): print('Error: Project names must begin with a letter and contain'\ ' only\nletters, numbers and underscores') - elif exists(project_name): - print('Error: Directory %r already exists' % project_name) elif _module_exists(project_name): print('Error: Module %r already exists' % project_name) else: return True return False + def _copytree(self, src, dst): + """ + Since the original function always creates the directory, to resolve + the issue a new function had to be created. It's a simple copy and + was reduced for this case. + + More info at: + https://github.com/scrapy/scrapy/pull/2005 + """ + ignore = IGNORE + names = os.listdir(src) + ignored_names = ignore(src, names) + + if not os.path.exists(dst): + os.makedirs(dst) + + for name in names: + if name in ignored_names: + continue + + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + if os.path.isdir(srcname): + self._copytree(srcname, dstname) + else: + copy2(srcname, dstname) + copystat(src, dst) + def run(self, args, opts): - if len(args) != 1: + if len(args) not in (1, 2): raise UsageError() + project_name = args[0] + project_dir = args[0] + + if len(args) == 2: + project_dir = args[1] + + if exists(join(project_dir, 'scrapy.cfg')): + self.exitcode = 1 + print('Error: scrapy.cfg already exists in %s' % abspath(project_dir)) + return if not self._is_valid_name(project_name): self.exitcode = 1 return - copytree(self.templates_dir, project_name, ignore=IGNORE) - move(join(project_name, 'module'), join(project_name, project_name)) + self._copytree(self.templates_dir, abspath(project_dir)) + move(join(project_dir, 'module'), join(project_dir, project_name)) for paths in TEMPLATES_TO_RENDER: path = join(*paths) - tplfile = join(project_name, + tplfile = join(project_dir, string.Template(path).substitute(project_name=project_name)) render_templatefile(tplfile, project_name=project_name, ProjectName=string_camelcase(project_name)) print("New Scrapy project %r, using template directory %r, created in:" % \ (project_name, self.templates_dir)) - print(" %s\n" % abspath(project_name)) + print(" %s\n" % abspath(project_dir)) print("You can start your first spider with:") - print(" cd %s" % project_name) + print(" cd %s" % project_dir) print(" scrapy genspider example example.com") @property @@ -80,4 +117,4 @@ class Command(ScrapyCommand): _templates_base_dir = self.settings['TEMPLATES_DIR'] or \ join(scrapy.__path__[0], 'templates') return join(_templates_base_dir, 'project') - \ No newline at end of file + diff --git a/tests/test_commands.py b/tests/test_commands.py index cf415a388..d25045cb9 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -73,6 +73,27 @@ class StartprojectTest(ProjectTest): self.assertEqual(1, self.call('startproject', 'wrong---project---name')) self.assertEqual(1, self.call('startproject', 'sys')) + def test_startproject_with_project_dir(self): + project_dir = mkdtemp() + self.assertEqual(0, self.call('startproject', self.project_name, project_dir)) + + assert exists(join(abspath(project_dir), 'scrapy.cfg')) + assert exists(join(abspath(project_dir), 'testproject')) + assert exists(join(join(abspath(project_dir), self.project_name), '__init__.py')) + assert exists(join(join(abspath(project_dir), self.project_name), 'items.py')) + assert exists(join(join(abspath(project_dir), self.project_name), 'pipelines.py')) + assert exists(join(join(abspath(project_dir), self.project_name), 'settings.py')) + assert exists(join(join(abspath(project_dir), self.project_name), 'spiders', '__init__.py')) + + self.assertEqual(0, self.call('startproject', self.project_name, project_dir + '2')) + + self.assertEqual(1, self.call('startproject', self.project_name, project_dir)) + self.assertEqual(1, self.call('startproject', self.project_name + '2', project_dir)) + self.assertEqual(1, self.call('startproject', 'wrong---project---name')) + self.assertEqual(1, self.call('startproject', 'sys')) + self.assertEqual(2, self.call('startproject')) + self.assertEqual(2, self.call('startproject', self.project_name, project_dir, 'another_params')) + class StartprojectTemplatesTest(ProjectTest):