Repository: sputt/req-compile

Issue:
req-compile does not handle stdin paths correctly.
When following docs for compiling a group of projects I get the following exception (using req-compile as an example package):

```
(.venv) C:\repos\req-compile>req-candidates --paths-only | req-compile -v
INFO:req_compile.metadata:Extracting metadata for C:\repos\req-compile
DEBUG:req_compile.metadata:Extracting directly from a source directory
INFO:req_compile.metadata:Attempting to fetch metadata from setup.py
DEBUG:req_compile.metadata:Setting root to C:\Users\NATHAN~1\AppData\Local\Temp\tmpuyi0wfb9_NonExtractor_req-compile
INFO:req_compile.metadata:Parsing setup.py setup.py
INFO:req_compile.metadata:Parsing from setup.cfg
Traceback (most recent call last):
  File "C:\repos\req-compile\.venv\Scripts\req-compile-script.py", line 11, in <module>
    load_entry_point('req-compile', 'console_scripts', 'req-compile')()
  File "c:\repos\req-compile\req_compile\cmdline.py", line 407, in compile_main
    run_compile(args.requirement_files,
  File "c:\repos\req-compile\req_compile\cmdline.py", line 214, in run_compile
    input_reqs = [_create_input_reqs(input_arg) for input_arg in input_args]
  File "c:\repos\req-compile\req_compile\cmdline.py", line 214, in <listcomp>
    input_reqs = [_create_input_reqs(input_arg) for input_arg in input_args]
  File "c:\repos\req-compile\req_compile\cmdline.py", line 179, in _create_input_reqs
    return DistInfo('-', None, list(itertools.chain(*[_create_stdin_input_req(line)
TypeError: 'DistInfo' object is not iterable
```

Discussion:

Patch:
diff --git a/req_compile/cmdline.py b/req_compile/cmdline.py
index 73ce41f..baf7f1f 100644
--- a/req_compile/cmdline.py
+++ b/req_compile/cmdline.py
@@ -3,7 +3,6 @@ from __future__ import print_function
 
 import argparse
 import datetime
-import itertools
 import logging
 import os
 import shutil
@@ -157,30 +156,39 @@ def _create_req_from_path(path):
     Returns:
 
     """
-    dist = req_compile.metadata.extract_metadata(path)
-    dist.meta = True
+    try:
+        dist = req_compile.metadata.extract_metadata(path)
+    except req_compile.metadata.MetadataError:
+        dist = None
+
     if dist is None:
         raise ValueError(
-            'Input arg "{}" is not directory containing setup.py or requirements file'.format(path))
+            'Input arg "{}" is not directory containing a valid setup.py'.format(path))
     return dist
 
 
-def _create_input_reqs(input_arg):
+def _create_input_reqs(input_arg, extra_sources):
     input_arg = input_arg.strip()
     if input_arg == '-':
         stdin_contents = sys.stdin.readlines()
 
         def _create_stdin_input_req(line):
             try:
-                return _create_input_reqs(line)
+                result = _create_req_from_path(line)
+                extra_sources.append(line)
+                return utils.parse_requirement('{}=={}'.format(*result.to_definition(None)))
             except ValueError:
-                return (utils.parse_requirement(line),)
+                return utils.parse_requirement(line)
 
-        return DistInfo('-', None, list(itertools.chain(*[_create_stdin_input_req(line)
-                                                          for line in stdin_contents])), meta=True)
+        reqs = (_create_stdin_input_req(line.strip())
+                for line in stdin_contents
+                if line.strip())
+        reqs = (req for req in reqs if req is not None)
+        return DistInfo('-', None, reqs, meta=True)
 
     if os.path.isfile(input_arg):
         return RequirementsFile.from_file(input_arg)
+
     return _create_req_from_path(input_arg)
 
 
@@ -211,14 +219,15 @@ def run_compile(input_args,
         else:
             input_args = ('.',)
 
-    input_reqs = [_create_input_reqs(input_arg) for input_arg in input_args]
-    if extra_sources:
-        # Add the sources provided to the search repos
-        repo = MultiRepository(*([SourceRepository(source) for source in extra_sources] + [repo]))
+    input_reqs = [_create_input_reqs(input_arg, extra_sources) for input_arg in input_args]
 
     constraint_reqs = None
     if constraint_files is not None:
-        constraint_reqs = [_create_input_reqs(input_arg) for input_arg in constraint_files]
+        constraint_reqs = [_create_input_reqs(input_arg, extra_sources) for input_arg in constraint_files]
+
+    if extra_sources:
+        # Add the sources provided to the search repos
+        repo = MultiRepository(*([SourceRepository(source) for source in extra_sources] + [repo]))
 
     try:
         results, roots = perform_compile(input_reqs, repo, extras=extras,


Test patch:
diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py
index 5e50ce9..10c3d8f 100644
--- a/tests/test_cmdline.py
+++ b/tests/test_cmdline.py
@@ -1,14 +1,17 @@
+import os
+
+import pkg_resources
 import pytest
 
-from req_compile.cmdline import compile_main
-import os
+from six import StringIO
+
+from req_compile.cmdline import compile_main, _create_input_reqs
 
 from req_compile.dists import DistInfo
 from req_compile.repos.findlinks import FindLinksRepository
 from req_compile.repos.pypi import PyPIRepository
 from req_compile.repos.solution import SolutionRepository
 from req_compile.repos.source import SourceRepository
-from req_compile import utils
 
 
 @pytest.fixture
@@ -62,3 +65,41 @@ def test_source_dirs_dont_hit_pypi(mocker, basic_compile_mock):
     compile_main(['source/myproj'])
     perform_compile_args = basic_compile_mock.mock_calls[0][1]
     assert perform_compile_args[0][0].name == 'myproj'
+
+
+@pytest.fixture
+def mock_stdin(mocker):
+    fake_stdin = StringIO()
+
+    def _write(value):
+        fake_stdin.write(value)
+        fake_stdin.seek(0, 0)
+
+    mocker.patch('sys.stdin', fake_stdin)
+    return _write
+
+
+def test_stdin_paths(mock_stdin):
+    """Verify that paths work correctly from stdin"""
+    mono_dir = os.path.join(os.path.dirname(__file__), 'repos', 'monorepo')
+    mono1 = os.path.join(mono_dir, 'pkg1')
+    mono2 = os.path.join(mono_dir, 'pkg2')
+    mono3 = os.path.join(mono_dir, 'subdir', 'pkg3')
+    mock_stdin(mono1 + '\n' + mono2 + '\n' + mono3 + '\n')
+
+    extra_sources = []
+    result = _create_input_reqs('-', extra_sources)
+
+    assert set(extra_sources) == {mono1, mono2, mono3}
+    assert set(result.reqs) == set(pkg_resources.parse_requirements(['pkg1==1.0.0', 'pkg2==2.0.1', 'pkg3==0.0.0']))
+
+
+def test_stdin_reqs(mock_stdin):
+    """Verify that lists of requirements work correctly from stdin, including comment and blank lines"""
+    mock_stdin('pytest\n# Comment\n\npytest-mock\n')
+
+    extra_sources = []
+    result = _create_input_reqs('-', extra_sources)
+
+    assert extra_sources == []
+    assert set(result.reqs) == set(pkg_resources.parse_requirements(['pytest', 'pytest-mock']))