Repository: nickpegg/posty

Issue:
`posty new` commands
Add a set of commands to create new posts or pages. Pull from an example file from the skeleton data.

Example CLI:
`posty new page "About me"` -> `pages/{{slug}}.yaml`
`posty new post "My new post"` -> `posts/{{date}}_{{slug}}.yaml`
`posty new post` -> `posts/{{date}}_new-post.yaml`

Discussion:

Patch:
diff --git a/posty/cli.py b/posty/cli.py
index 5285708..a8e0ee0 100644
--- a/posty/cli.py
+++ b/posty/cli.py
@@ -57,6 +57,42 @@ def build(output, config):
     shutil.copytree('media', os.path.join(output, 'media'))
 
 
+@cli.group(name='new')
+def _new():
+    """
+    Create a new post or page
+    """
+    pass
+
+
+@_new.command()
+@click.option(
+    '--name',
+    help='Name of the new page',
+    default='New Page',
+)
+def page(name):
+    """
+    Create a new page from the template
+    """
+    site = Site()
+    site.new_page(name=name)
+
+
+@_new.command()
+@click.option(
+    '--name',
+    help='Name of the new post',
+    default='New Post',
+)
+def post(name):
+    """
+    Create a new page from the template
+    """
+    site = Site()
+    site.new_post(name=name)
+
+
 @cli.group(name='import')
 def _import():
     """
diff --git a/posty/page.py b/posty/page.py
index 873f356..c5874af 100644
--- a/posty/page.py
+++ b/posty/page.py
@@ -26,6 +26,20 @@ class Page(Model):
 
         return cls(payload, config=config)
 
+    def to_yaml(self):
+        """
+        Returns a string of the YAML and text representation of this Post.
+        This is the reverse of from_yaml
+        """
+        metadata = {'title': self['title']}
+        if self['parent']:
+            metadata['parent'] = self['parent']
+        output = yaml.dump(metadata, default_flow_style=False)
+        output += "---\n"
+        output += self['body']
+
+        return output
+
     def validate(self):
         required_fields = ('title', 'body')
         for field in required_fields:
diff --git a/posty/post.py b/posty/post.py
index 060c2c1..fbd2b0d 100644
--- a/posty/post.py
+++ b/posty/post.py
@@ -39,6 +39,32 @@ class Post(Model):
 
         return cls(post, config=config)
 
+    def to_yaml(self):
+        """
+        Returns the YAML and text representation of this Post. This is the
+        reverse of ``from_yaml()``
+        """
+        metadata = {
+            'title': self['title'],
+            'date': self['date'],
+            'tags': self['tags'],
+        }
+        body = self['body']
+
+        output = yaml.dump(metadata, default_flow_style=False)
+
+        if self['blurb'] != self['body']:
+            output += "---\n"
+            output += self['blurb'].strip()
+            output += "\n"
+
+            body = body.replace(self['blurb'], '')
+
+        output += "---\n"
+        output += body.strip()
+
+        return output
+
     def validate(self):
         required_fields = ('title', 'date', 'blurb', 'body')
         for field in required_fields:
diff --git a/posty/site.py b/posty/site.py
index 04bebff..0aeb50f 100644
--- a/posty/site.py
+++ b/posty/site.py
@@ -1,4 +1,5 @@
 from collections import Counter
+import datetime
 import os.path
 import shutil
 
@@ -175,3 +176,44 @@ class Site(object):
         )
 
         return copyright
+
+    def new_post(self, name="New Post"):
+        """
+        Create a new post in the site directory from the skeleton post
+        """
+        post_dir = os.path.join(self.site_path, 'posts')
+        if not os.path.exists(post_dir):
+            raise PostyError('You must initialize the site first')
+
+        date = datetime.date.today()
+        filename = '{}_{}.yaml'.format(date, slugify(name))
+        post_path = os.path.join(post_dir, filename)
+
+        skel_path = os.path.join(os.path.dirname(__file__),
+                                 'skel/posts/1970-01-01_new-post.yaml')
+        post = Post.from_yaml(open(skel_path).read(), config=self.config)
+        post['title'] = name
+        post['date'] = date
+
+        with open(post_path, 'w') as output_file:
+            output_file.write(post.to_yaml())
+
+    def new_page(self, name="New Page"):
+        """
+        Create a new page in the site directory from the skeleton page
+        """
+        page_dir = os.path.join(self.site_path, 'pages')
+        if not os.path.exists(page_dir):
+            raise PostyError('You must initialize the site first')
+
+        filename = '{}.yaml'.format(slugify(name))
+        page_path = os.path.join(page_dir, filename)
+
+        skel_path = os.path.join(os.path.dirname(__file__),
+                                 'skel/pages/new-page.yaml')
+
+        page = Page.from_yaml(open(skel_path).read(), config=self.config)
+        page['title'] = name
+
+        with open(page_path, 'w') as output_file:
+            output_file.write(page.to_yaml())
diff --git a/posty/skel/pages/new-page.yaml b/posty/skel/pages/new-page.yaml
new file mode 100644
index 0000000..73cc0f6
--- /dev/null
+++ b/posty/skel/pages/new-page.yaml
@@ -0,0 +1,4 @@
+title: New Page
+parent: None
+---
+This is your new page. Write what you want here!
diff --git a/posty/skel/posts/1970-01-01_new-post.yaml b/posty/skel/posts/1970-01-01_new-post.yaml
new file mode 100644
index 0000000..9057105
--- /dev/null
+++ b/posty/skel/posts/1970-01-01_new-post.yaml
@@ -0,0 +1,11 @@
+title: New Post
+date: 1970-01-01
+tags:
+    - tag1
+    - tag2
+---
+This the the summary/first paragraph of your new post
+---
+This is the rest of the post.
+
+Write what you want here!


Test patch:
diff --git a/tests/fixtures/site/posts/multi-paragraph.yaml b/tests/fixtures/site/posts/multi-paragraph.yaml
index 1c55996..633e099 100644
--- a/tests/fixtures/site/posts/multi-paragraph.yaml
+++ b/tests/fixtures/site/posts/multi-paragraph.yaml
@@ -1,8 +1,8 @@
-title: Multi-paragraph Post
 date: 2017-01-14
 tags:
-    - blah
-    - test
+- blah
+- test
+title: Multi-paragraph Post
 ---
 This is a post that has multiple paragraphs, where the first paragraph should get converted into a blurb.
 ---
diff --git a/tests/test_page.py b/tests/test_page.py
index d06c567..62feb33 100644
--- a/tests/test_page.py
+++ b/tests/test_page.py
@@ -7,15 +7,19 @@ from posty.page import Page
 from .fixtures import config    # noqa
 
 
+@pytest.fixture
+def page_contents():
+    path = os.path.join(os.path.dirname(__file__), 'fixtures', 'site', 'pages',
+                        'test.yaml')
+    return open(path).read()
+
+
 @pytest.fixture     # noqa
-def page(config):
+def page(config, page_contents):
     """
     Basic top-level page (has no parent)
     """
-    path = os.path.join(os.path.dirname(__file__), 'fixtures', 'site', 'pages',
-                        'test.yaml')
-    contents = open(path).read()
-    return Page.from_yaml(contents, config=config)
+    return Page.from_yaml(page_contents, config=config)
 
 
 class TestValidation(object):
@@ -44,3 +48,7 @@ def test_url(page):
     expected_url = 'http://example.org/test/{}/'.format(page['slug'])
 
     assert page.url() == expected_url
+
+
+def test_to_yaml(page, page_contents):
+    assert page_contents.strip() == page.to_yaml()
diff --git a/tests/test_post.py b/tests/test_post.py
index d27217d..478f834 100644
--- a/tests/test_post.py
+++ b/tests/test_post.py
@@ -8,15 +8,19 @@ from posty.post import Post
 from .fixtures import config    # noqa
 
 
+@pytest.fixture
+def post_contents():
+    path = os.path.join(os.path.dirname(__file__), 'fixtures', 'site', 'posts',
+                        'multi-paragraph.yaml')
+    return open(path).read()
+
+
 @pytest.fixture     # noqa
-def post(config):
+def post(config, post_contents):
     """
     Basic post
     """
-    path = os.path.join(os.path.dirname(__file__), 'fixtures', 'site', 'posts',
-                        'multi-paragraph.yaml')
-    contents = open(path).read()
-    return Post.from_yaml(contents, config=config)
+    return Post.from_yaml(post_contents, config=config)
 
 
 class TestValidation(object):
@@ -46,3 +50,7 @@ def test_url(post):
                                                                   post['slug'])
 
     assert post.url() == expected_url
+
+
+def test_to_yaml(post, post_contents):
+    assert post_contents.strip() == post.to_yaml()
diff --git a/tests/test_site.py b/tests/test_site.py
index ecee2af..1468563 100644
--- a/tests/test_site.py
+++ b/tests/test_site.py
@@ -1,3 +1,6 @@
+import datetime
+import os
+
 from .fixtures import site  # noqa
 
 
@@ -32,3 +35,27 @@ def test_post_sorting(site):    # noqa
 def test_copyright(site):   # noqa
     site.load()
     assert site.copyright == 'Copyright 2010 - 2017, Jimbo Jawn'
+
+
+def test_new_page(site):    # noqa
+    site.new_page()
+    new_page_path = os.path.join(site.site_path, 'pages', 'new-page.yaml')
+    assert os.path.exists(new_page_path)
+
+    site.new_page('Neato page')
+    new_page_path = os.path.join(site.site_path, 'pages', 'neato-page.yaml')
+    assert os.path.exists(new_page_path)
+
+
+def test_new_post(site):    # noqa
+    date = datetime.date.today()
+
+    site.new_post()
+    filename = '{}_new-post.yaml'.format(date)
+    expected_path = os.path.join(site.site_path, 'posts', filename)
+    assert os.path.exists(expected_path)
+
+    site.new_post('Neato Post')
+    filename = '{}_neato-post.yaml'.format(date)
+    expected_path = os.path.join(site.site_path, 'posts', filename)
+    assert os.path.exists(expected_path)