Repository: conan-io/conan

Issue:
Conan new command with template packages
Template based generation of conan packages with "test_package" included for:
- Header only libraries
- Normal, with source.

It should generate the skeleton of the package, including the test_package based on:
- Package name, package version, user, channel (set default, can be changed later)
- Header or source



Discussion:

Resolving patch:
diff --git a/conans/client/command.py b/conans/client/command.py
--- a/conans/client/command.py
+++ b/conans/client/command.py
@@ -21,7 +21,7 @@
 from conans.model.version import Version
 from conans.client.migrations import ClientMigrator
 import hashlib
-from conans.util.files import rmdir, load
+from conans.util.files import rmdir, load, save_files
 from argparse import RawTextHelpFormatter
 import re
 from conans.client.runner import ConanRunner
@@ -154,6 +154,45 @@ def _test_check(self, test_folder, test_folder_name):
 
  """ % (test_folder_name))
 
+    def new(self, *args):
+        """ create a new package template conanfile.py and other optional files
+        """
+        parser = argparse.ArgumentParser(description=self.new.__doc__, prog="conan new",
+                                         formatter_class=RawTextHelpFormatter)
+        parser.add_argument("name", help='Package name, e.g.: Poco/1.7.3@user/testing')
+        parser.add_argument("-t", "--test", action='store_true', default=False,
+                            help='Create test_package skeleton to test package')
+        parser.add_argument("-i", "--header", action='store_true', default=False,
+                            help='Create a headers only package')
+        parser.add_argument("-c", "--pure_c", action='store_true', default=False,
+                            help='Create a C language package only package (non-headers)')
+
+        args = parser.parse_args(*args)
+
+        root_folder = os.getcwd()
+        try:
+            name, version, user, channel = ConanFileReference.loads(args.name)
+        except:
+            raise ConanException("Bad parameter, please use full package name,"
+                                 "e.g: MyLib/1.2.3@user/testing")
+        from conans.client.new import (conanfile, conanfile_header, test_conanfile, test_cmake,
+                                       test_main)
+        if args.header:
+            files = {"conanfile.py": conanfile_header.format(name=name, version=version)}
+        else:
+            files = {"conanfile.py": conanfile.format(name=name, version=version)}
+            if args.pure_c:
+                config = "\n    def config(self):\n        del self.settings.compiler.libcxx"
+                files["conanfile.py"] = files["conanfile.py"] + config
+        if args.test:
+            files["test_package/conanfile.py"] = test_conanfile.format(name=name, version=version,
+                                                                       user=user, channel=channel)
+            files["test_package/CMakeLists.txt"] = test_cmake
+            files["test_package/example.cpp"] = test_main
+        save_files(root_folder, files)
+        for f in sorted(files):
+            self._user_io.out.success("File saved: %s" % f)
+
     def test_package(self, *args):
         """ build and run your package test. Must have conanfile.py with "test"
         method and "test_package" subfolder with package consumer test project
diff --git a/conans/client/new.py b/conans/client/new.py
new file mode 100644
--- /dev/null
+++ b/conans/client/new.py
@@ -0,0 +1,111 @@
+conanfile = """from conans import ConanFile, CMake, tools
+import os
+
+
+class {name}Conan(ConanFile):
+    name = "{name}"
+    version = "{version}"
+    license = "<Put the package license here>"
+    url = "<Package recipe repository url here, for issues about the package"
+    settings = "os", "compiler", "build_type", "arch"
+    options = {{"shared": [True, False]}}
+    default_options = "shared=False"
+    generators = "cmake"
+
+    def source(self):
+       self.run("git clone https://github.com/memsharded/hello.git")
+       self.run("cd hello && git checkout static_shared")
+       # This small hack might be useful to guarantee proper /MT /MD linkage in MSVC
+       # if the packaged project doesn't have variables to set it properly
+       tools.replace_in_file("hello/CMakeLists.txt", "PROJECT(MyHello)", '''PROJECT(MyHello)
+include(${{CMAKE_BINARY_DIR}}/conanbuildinfo.cmake)
+conan_basic_setup()''')
+
+    def build(self):
+        cmake = CMake(self.settings)
+        shared = "-DBUILD_SHARED_LIBS=ON" if self.options.shared else ""
+        self.run('cmake hello %s %s' % (cmake.command_line, shared))
+        self.run("cmake --build . %s" % cmake.build_config)
+
+    def package(self):
+        self.copy("*.h", dst="include", src="hello")
+        self.copy("*hello.lib", dst="lib", keep_path=False)
+        self.copy("*.dll", dst="bin", keep_path=False)
+        self.copy("*.so", dst="lib", keep_path=False)
+        self.copy("*.a", dst="lib", keep_path=False)
+
+    def package_info(self):
+        self.cpp_info.libs = ["hello"]
+"""
+
+
+conanfile_header = """from conans import ConanFile, tools
+import os
+
+
+class {name}Conan(ConanFile):
+    name = "{name}"
+    version = "{version}"
+    license = "<Put the package license here>"
+    url = "<Package recipe repository url here, for issues about the package"
+    # No settings/options are necessary, this is header only
+
+    def source(self):
+        '''retrieval of the source code here. Remember you can also put the code in the folder and
+        use exports instead of retrieving it with this source() method
+        '''
+        #self.run("git clone ...") or
+        #tools.download("url", "file.zip")
+        #tools.unzip("file.zip" )
+
+    def package(self):
+        self.copy("*.h", "include")
+"""
+
+
+test_conanfile = """from conans import ConanFile, CMake
+import os
+
+
+channel = os.getenv("CONAN_CHANNEL", "{channel}")
+username = os.getenv("CONAN_USERNAME", "{user}")
+
+
+class {name}TestConan(ConanFile):
+    settings = "os", "compiler", "build_type", "arch"
+    requires = "{name}/{version}@%s/%s" % (username, channel)
+    generators = "cmake"
+
+    def build(self):
+        cmake = CMake(self.settings)
+        self.run('cmake "%s" %s' % (self.conanfile_directory, cmake.command_line))
+        self.run("cmake --build . %s" % cmake.build_config)
+
+    def imports(self):
+        self.copy("*.dll", "bin", "bin")
+        self.copy("*.dylib", "bin", "bin")
+
+    def test(self):
+        os.chdir("bin")
+        self.run(".%sexample" % os.sep)
+"""
+
+test_cmake = """PROJECT(PackageTest)
+cmake_minimum_required(VERSION 2.8.12)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup()
+
+ADD_EXECUTABLE(example example.cpp)
+TARGET_LINK_LIBRARIES(example ${CONAN_LIBS})
+"""
+
+test_main = """#include <iostream>
+#include "hello.h"
+
+int main() {
+    hello();
+    std::cout<<"*** Running example, will fail by default, implement yours! ***\\n";
+    return -1; // fail by default, remember to implement your test
+}
+"""


Test patch:
diff --git a/conans/test/command/new_test.py b/conans/test/command/new_test.py
new file mode 100644
--- /dev/null
+++ b/conans/test/command/new_test.py
@@ -0,0 +1,57 @@
+import unittest
+from conans.test.tools import TestClient
+import os
+from conans.util.files import load
+
+
+class NewTest(unittest.TestCase):
+
+    def new_test(self):
+        """ Test that the user can be shown and changed, and it is reflected in the
+        user cache localdb
+        """
+        client = TestClient()
+        client.run('new MyPackage/1.3@myuser/testing -t')
+        root = client.current_folder
+        self.assertTrue(os.path.exists(os.path.join(root, "conanfile.py")))
+        content = load(os.path.join(root, "conanfile.py"))
+        self.assertIn('name = "MyPackage"', content)
+        self.assertIn('version = "1.3"', content)
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/conanfile.py")))
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/CMakeLists.txt")))
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/example.cpp")))
+        # assert they are correct at least
+        client.run("export myuser/testing")
+        client.run("info test_package")
+        self.assertIn("MyPackage/1.3@myuser/testing", client.user_io.out)
+
+    def new_header_test(self):
+        """ Test that the user can be shown and changed, and it is reflected in the
+        user cache localdb
+        """
+        client = TestClient()
+        client.run('new MyPackage/1.3@myuser/testing -t -i')
+        root = client.current_folder
+        self.assertTrue(os.path.exists(os.path.join(root, "conanfile.py")))
+        content = load(os.path.join(root, "conanfile.py"))
+        self.assertIn('name = "MyPackage"', content)
+        self.assertIn('version = "1.3"', content)
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/conanfile.py")))
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/CMakeLists.txt")))
+        self.assertTrue(os.path.exists(os.path.join(root, "test_package/example.cpp")))
+        # assert they are correct at least
+        client.run("export myuser/testing")
+        client.run("info test_package")
+        self.assertIn("MyPackage/1.3@myuser/testing", client.user_io.out)
+
+    def new_without_test(self):
+        """ Test that the user can be shown and changed, and it is reflected in the
+        user cache localdb
+        """
+        client = TestClient()
+        client.run('new MyPackage/1.3@myuser/testing')
+        root = client.current_folder
+        self.assertTrue(os.path.exists(os.path.join(root, "conanfile.py")))
+        self.assertFalse(os.path.exists(os.path.join(root, "test_package/conanfile.py")))
+        self.assertFalse(os.path.exists(os.path.join(root, "test_package/CMakeLists.txt")))
+        self.assertFalse(os.path.exists(os.path.join(root, "test_package/example.cpp")))