반응형

사실...여러 예제와 뤼튼 AI를 이용해서 하루 종일 고생하며 방법을 찾아봤는데, 지금도 왜 되었는지 잘 모르겠습니다. 예제를 따라 해도 안되던게 갑자기 됩니다. 우선 코드만 남겨놓습니다.

 

 

 

1. 마크다운 파일 읽어오는 옵션

'gatsby-source-filesystem'은 기본적으로 설치되었을겁니다. 마크다운 파일들을 모아놓는 곳을 아래와 같이 설정해줍니다.

// gatsby-config.js
module.exports = {
  siteMetadata: { ... },
  plugins: [
    ...,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `posts`,
        path: `${__dirname}/content/posts`,
      },
    },
    ...
  ],
};

2. gatsby-plugin-image 플러그인

'gatsby-plugin-image' 플러그인도 기본으로 설치가 되어있을 확률이 높습니다. Gatsby에서 이미지를 렌더링 할 때 사용하는 플러그인입니다. 이미지들에 대해 자동으로 인식하여 속성정보를 생성해주지만, 가장 중요한 것 - 이미지 폴더를 지정해줘야합니다.

module.exports = {
  ...,
  plugins: [
    `gatsby-plugin-image`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `images`,
        path: `${__dirname}/content/posts/images`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `posts`,
        path: `${__dirname}/content/posts`,
      },
    },

 

3. 마크다운 파일

아래 정도의 frontmatter를 작성해줍니다. 샘플로 몇개를 더 만들어놓습니다. 그리고 thumbnail경로에 예시 코드와 맞도록 이미지를 준비해줍니다.

---
title: Todo List
date: "2022-05-10T22:12:03.284Z"
description: 남은 할일 목록
slug: page1
thumbnail: './images/page1.png'
---


- 댓글기능
- 검색최적화(SEO)
- 카테고리 만들기

 

4./gatsby-node.js

이 부분에서는 이전 포스트 대비 특별히 손댈 것은 없습니다. thumbnail노드 하위의 속성은 gatsby-plugin-image 플러그인을 통해 인식된 이미지들로 인해 자동으로 생성됩니다.

const path = require('path');

exports.createPages = ({ graphql, actions }) => {
  const { createPage } = actions;

  const blogPostTemplate = path.resolve('src/templates/md-files.js');

  return graphql(`
    {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `).then((result) => {
    if (result.errors) {
      return Promise.reject(result.errors);
    }

    result.data.allMarkdownRemark.edges.forEach(({ node }) => {
      createPage({
        path: node.fields.slug,
        component: blogPostTemplate,
        context: {
          slug: node.fields.slug,
        },
      });
    });
  });
};

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions;

  if (node.internal.type === `MarkdownRemark`) {
    const slug = path.basename(node.fileAbsolutePath, '.md');

    createNodeField({
      node,
      name: `slug`,
      value: `/${slug}`,
    });
  }
};

6. src/pages/index.js

이전 포스팅에서 작성한 내용에서, 각 포스팅의 타이틀 스타일을 변경해주고, 표시되는 순서를 작성일의 역순정렬로 변ㄱ여하였습니다.

import React from "react"
import { useStaticQuery, Link, graphql } from "gatsby"
import Layout from "../components/layout"

export default ({ data }) => {
  console.log(data)
  return (
    <Layout>
      <div>
        <h1>
          A World of Hello World and Todo List
        </h1>
        <h4>{data.allMarkdownRemark.totalCount} Posts</h4>
        {data.allMarkdownRemark.edges.map(({ node }) => (
          <div key={node.id} style={{
            marginBottom: "50px",
          }}>
            <h2>
              <Link to={node.fields.slug}
                style={{
                  color: "#555",           
                  textDecoration: "none",
                }}>
                {node.frontmatter.title}{" "}
                <span>
                  — {node.frontmatter.date}
                </span>
              </Link>
            </h2>
            <p>{node.excerpt}</p>
          </div>
        ))}
      </div>
    </Layout>
  )
}

export const query = graphql`
  query {
    allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
            description
          }
          excerpt
          fields{
            slug
          }
        }
      }
    }
  }
`

 

7. src/templates/md-files.js

이부분이 마크다운 파일들을 읽어서 동일한 템플릿 형태로 표현해주는 부분입니다. 여기서 gatsby-plugin-image 플러그인에서 제공하는 GatsbyImage 컴포넌트와 getImage 메서드를 활용합니다. gatsby-plugin-image 역시 이미 설치되어있을 확률이 높습니다. 

import React from "react"
import { graphql } from "gatsby"
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import Layout from "../components/layout"

export default function Template({ data }) {
  
  const { markdownRemark } = data
  const { frontmatter, html } = markdownRemark
  const thumbnailImg = getImage(frontmatter.thumbnail?.childImageSharp?.gatsbyImageData)

  return (
    <Layout>
      <div className="blog-post-container">
        <div className="blog-post">
          <GatsbyImage image={thumbnailImg} alt="Thumbnail" />
          <h1>{frontmatter.title}</h1>
          <h2>{frontmatter.date}</h2>
          <div dangerouslySetInnerHTML={{ __html: html }} />
        </div>
      </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        date(formatString: "MMMM DD, YYYY")        
        title  
        thumbnail {
          childImageSharp {
            gatsbyImageData
          }
        }
      }
    }  
  }
`

 

8. gatsby-remark-images 설치 및 설정

gatsby-remark-images플러그인을 설치한 후 gatsby-config.js파일에 아래와 같이 추가합니다. 이 플러그인은 마크다운 파일의 이미지를 최적화하여 사용할 수 있도록 도와주는 플러그인입니다. 화면에서 이상하게 나오던 이미지들이 아래의 설정 이후정상적으로 나옵니다. 

{
      resolve: `gatsby-transformer-remark`,
      options: {
        plugins: [
          {
            resolve: `gatsby-remark-images`,
            options: {
              maxWidth: 800,
            },
          },
        ],
      },
    },

 

<결과>

반응형
반응형

[ 목차 ]

    오랫만에 간단히 PC에서 XML파일의 텍스트를 읽어올 일이 있어서, 간단히 닷넷 winform으로 만들어봤습니다. winform은 정말 오랫만이라, 또 잊어버리기 전에 기록으로 남깁니다.

     

    해당 프로그램은 닷넷6.0, VSCode에서 만들어졌습니다.

     

    1. 닷넷 Winform 시작

    적당한 이름의 프로젝트 폴더를 만들고, 아래의 명령어어로 winform 프로젝트를 생성합니다.

    dotnet new winforms // 끝에 s가 들어감을 확인

     

    2. Form1.Designer.cs 으로 요소 추가하기

    우선 필요한 버튼, 파일경로를 나타내는 textbox, 그리고 추출 결과물을 표현할 textbox를 추가합니다.

    namespace wf01_xml;
    
    partial class Form1
    {
        /// <summary>
        ///  Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;
    
        /// <summary>
        ///  Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    
        #region Windows Form Designer generated code
    
        /// <summary>
        ///  Required method for Designer support - do not modify
        ///  the contents of this method with the code editor.
        /// </summary>
        private Button button1;
        private TextBox textBox1;
        private TextBox result1;
        private Panel panel1;
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(1100, 450);
            this.Text = "Form1";
    
            // 버튼 컨트롤 생성 및 설정
            this.button1 = new System.Windows.Forms.Button();
            this.button1.Location = new System.Drawing.Point(10, 10);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(70, 50);
            this.button1.Text = "Click Me!";
            this.button1.UseVisualStyleBackColor = true;
            // this.button1.Click += new System.EventHandler(this.button1_Click);  // 클릭 이벤트 핸들러 등록
    
            // 버튼을 폼에 추가
            this.Controls.Add(this.button1);
    
            // 텍스트 박스 생성 및 추가
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.textBox1.Location = new System.Drawing.Point(10, 70);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(500, 500);
            this.textBox1.Text = "";
            // this.textBox1.UseVisualStyleBackColor = true;
            this.Controls.Add(this.textBox1);
    
            // 패널 생성 및 추가  
            this.panel1 = new System.Windows.Forms.Panel();   
            this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 
                | System.Windows.Forms.AnchorStyles.Right | System.Windows.Forms.AnchorStyles.Bottom)));
            this.panel1.Location = new System.Drawing.Point(10, 100);   
            this.panel1.Name = "panel1";        
            this.panel1.Size = new System.Drawing.Size(1090,330);
            // this.panel1.TabIndex = 0;
            // this.panel1.Width = this.ClientSize.Width;
            // this.panel1.Height = this.ClientSize.Width;
            // this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.panel1.AutoScroll = true;
            this.panel1.BackColor = Color.DimGray;
            
            this.Controls.Add(this.panel1);
    
            // 텍스트 박스 생성 및 추가
            this.result1 = new System.Windows.Forms.TextBox();
            this.result1.Name = "resul1";
            this.result1.Multiline = true;    
            this.result1.Location = new System.Drawing.Point(0, 0);     
            this.result1.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
            this.result1.Dock = System.Windows.Forms.DockStyle.Fill; // Dock 속성 설정
            this.result1.Text = "";
    
            // 텍스트 박스의 크기와 위치를 조정               
            // this.result1.Width = this.panel1.ClientRectangle.Width;
            // this.result1.Height = this.panel1.ClientRectangle.Height;        
            this.panel1.Controls.Add(this.result1);
    
    
    
    
    
        }
    
        #endregion
    }

     

    3. Form1.cs

    버튼을 누르면 파일탐색이 가능한 filedialog를 생성하고, 선택한 XML 파일로부터 데이터를 처리하도록 구성합니다. 특히 XML에서 각 노드의 값을 가져오는 부분은 불러오고자하는 파일의 형식에 맞게 수정하시기 바랍니다. 아직 미완성인 부분...

    namespace wf01_xml;
    using System;
    using System.Text;
    using System.Xml;
    
    public partial class Form1 : Form
    {
    	public Form1()
    	{        
    		InitializeComponent();
    		this.button1.Click += new System.EventHandler(this.button1_Click);
    	}
    
    	private void button1_Click(object sender, EventArgs e)
    	{
    		OpenFileDialog openFileDlg = new OpenFileDialog();        
    		openFileDlg.Filter = "XML Files (*.xml)|*.xml";
    		openFileDlg.ShowDialog();
    		// MessageBox.Show("Hello World!");
    		if (openFileDlg.FileName.Length > 0)
    		{
    			foreach (string Filename in openFileDlg.FileNames)
    			{
    				this.textBox1.Text = Filename;                
    				
    				XmlDocument xmlDoc = new XmlDocument();  // XmlDocument 객체 생성
    				xmlDoc.Load(Filename);                 // XML 파일 로드                               
    				XmlNodeList allNodes = xmlDoc.SelectNodes("//bookstore//*");
    				StringBuilder sb = new StringBuilder();
    				
    				foreach(XmlNode node in allNodes){
    					if(node.NodeType == XmlNodeType.Element){
    						sb.Append(node.InnerText);
    					}
    					else if (node.NodeType == XmlNodeType.Text){
    						sb.Append(node.Value );
    					}
    					// sb.Append("\n");
    					sb.Append(Environment.NewLine);
    				}
    
    				this.result1.Text = sb.ToString();
    			}
    		}
    	}
    }

     

    4. 샘플XML (Book.xml)

    <?xml version="1.0" encoding="UTF-8"?>
    <bookstore>
      <book category="children">
        <title lang="en">Harry Potter</title>
        <author>J.K. Rowling</author>
        <year>2005</year>
        <price>29.99</price>
      </book>
      <book category="web">
        <title lang="en">Learning XML</title>
        <author>Erik T. Ray</author>
        <year>2003</year>
        <price>39.95</price>
      </book>
    </bookstore>

     

    <결과>

    부모 노드에서는 자식노드들의 값을 모두 출력합니다. 원하는 결과는 아니지만 조금 더 수정하면 비슷하게 사용은 가능할 것 같습니다.

    반응형
    반응형

     

    정적사이트 생성기(SSG, Static Site Generator)에 대해 공부하다보니 다시 Gatsby에 대한 흥미가 돌기 시작해서 오랫만에 Gatsby로 프로젝트를 만들어봤습니다. 예전에 이걸 공부했던 적이 있었나 싶을 정도로 하나도 기억이 안나더군요. React에 대한 두려움으로 React 계통의 프레임워크들은 무조건 멀리했었던 것 같기도 하고, 사실은 Gatsby, GraphQL에 대해 정확히 공부한적이 없었던 것 같기도 하네요. 그래서 오늘은 설치하고 마크다운 문서 표기하기까지 알게된 내용을 정리해볼까 합니다.

     

    [ 목차 ]

       

      1. Gatsby란?

      Gatsby는 서두에 말했듯이 React에 기반한 정적사이트 생성기(SSG, Static Site Generator)입니다. 예전부터 Ruby on Rails로 작성된 Jekyll 이 가장 많이 사용되었었는데, React에 기반한 점, GraphQL을 사용하여 이미지, 컨텐츠 등 데이터를 빠르고 쉽게 반영할 수 있는점 등으로 매우 큰 인기를 끌고 있습니다. 거기다 다양한 테마를 적용한 스타터 킷이 존재하여 사용자 편의성을 지원하고 있습니다. 이전 포스트(정적 사이트 생성기 (SSG, Static Site Generator) 로 나만의 블로그를 만들어보자 (tistory.com))에서도 소개했듯이 다양한 SSG가 존재하지만 그 중에서도 Gatsby는 만들어진 이래 항상 최상위권을 유지하고 있습니다. 

        이제 한단계씩 시작해보겠습니다. 이번 프로젝트에서도 마크다운 문서들을 이용해 각 URL별로 문서를 표시하는 기능을 만들어보겠습니다.

       

       

      2. 설치

      아래의 명령어로 글로벌 설치를 합니다.

      npm i -g gatsby-cli

       

      3. 프로젝트 생성

      gatsby new [프로젝트(폴더) 이름]
      
      //현재 폴더에 프로젝트를 구성할 경우 폴더명(".") 입력
      gatsby new .  // 개인적으로 이 방법 자주씀

       참고로, 스타터킷으로 시작하는 것도 좋은 방법입니다.(Starters Library | Gatsby (gatsbyjs.com)) 하지만 저는 아직 실력이 부족하여 남이 써놓은 코드를 아직 수정을 못하다보니, 직접 하나씩 만들어가고 있습니다.

       

      4. 결과 보기

      package.json에서 "script"부분을 조금 수정하여, develop 대신 dev로 간단히 바꿔놓았습니다.

      //from
      "develop": "gatsby develop"
      
      // to
      "dev": "gatsby develop",

      이제 아래의 명령어로 시작하면..

      npm run dev

      아래와 같이 2개의 URL이 주어지는데, 첫번째 URL이 결과 화면이고, 두번째는 graphql 관련 페이지입니다.

       

       

       

      5. Index페이지에서 마크다운 문서 리스트 보이기

      다음으로 Index페이지에 마크다운 문서들의 목록을 보여주도록 구성하겠습니다. 우선 마크다운 샘플 문서를 3개정도 만들어줍니다. 위치는 루트 디렉토리 내에 /content/posts폴더를 새로 만들어줍니다.

       

      [content/posts/post1.md] 새 폴더와 함께 새 파일 생성

      ---
      title: My Markdown Page
      date: "2023-07-25T22:12:03.284Z"
      description: This is an example of a Markdown page.
      slug: page1
      ---
      
      # My Markdown Page
      
      This is an example of a Markdown page. You can use Markdown syntax to format the content of your pages. 
      
      ## Subheading
      
      Here's another section with a subheading. You can use various formatting options such as **bold**, *italic*, and `code` to add emphasis.

      비슷하게 post2.md, post3.md파일을 생성해줍니다.

      slug는 추후 우리가 접속하게될 url주소입니다. post2.md에는 slug: page2, post3.md에는 slug: page3라고 작성합니다.

       

      [gatsby-config.js파일 plugin설정]

      gatsby-config.js파일에서 두가지 설정을 해주어야 합니다.

      첫째는 `gatsby-source-filesystem` 플러그인으로 posts폴더를 인식시켜주는 것이고,

      둘째는 `gatsby-transformer-remark` 를 이용해 마크다운 문서를 HTML로 변환시켜주는 것입니다.

       

      <posts 폴더 인식>

      gatsby-config.js파일의 plugin 부분을 보면 `gatsby-source-filesystem`을 이용해 images폴더를 설정한 부분이 있습니다. 이미 해당 플러그인이 설치가 되있다는 얘기입니다. package.json에서도 확인이 가능합니다. 그 아래쪽에 한단락을 복사해서 posts폴더를 인식할 수 있도록 설정을 추가합니다.

      module.exports = {
        siteMetadata: {
          title: `Gatsby Default Starter`,
          description: `Kick off your next, great Gatsby project with this default starter. This barebones starter ships with the main Gatsby configuration files you might need.`,
          author: `@gatsbyjs`,
          siteUrl: `https://gatsbystarterdefaultsource.gatsbyjs.io/`,
        },
        plugins: [
          `gatsby-plugin-image`,
          {
            resolve: `gatsby-source-filesystem`,
            options: {
              name: `images`,
              path: `${__dirname}/src/images`,
            },
          },
          { // 추가 여기서부터
            resolve: `gatsby-source-filesystem`,
            options: {
              name: `posts`,
              path: `${__dirname}/content/posts`,
            },
          }, // 여기까지
          `gatsby-transformer-sharp`,
          `gatsby-plugin-sharp`,
          {
            resolve: `gatsby-plugin-manifest`,
            options: {
              name: `gatsby-starter-default`,
              short_name: `starter`,
              start_url: `/`,
              background_color: `#663399`,
              // This will impact how browsers show your PWA/website
              // https://css-tricks.com/meta-theme-color-and-trickery/
              // theme_color: `#663399`,
              display: `minimal-ui`,
              icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site.
            },
          },
        ],
      }

      <마크다운 문서 변환>

      마크다운 문서 변환을 위해서는 `gatsby-transformer-remark` 플러그인을 우선 설치해야합니다.

      npm i gatsby-transformer-remark

      다음으로 gatsby-config.js파일에 해당 플러그인이 적용되도록 한줄 추가해넣습니다.

      <src/pages/index.js 파일 수정>

      index.js파일을 아직은 잘 모르겠지만, 아래와 같이 수정합니다. 아래부분은 GraphQL을 이용해 내가만든 컨텐츠(md파일들)로부터 정보를 가져오는 부분입니다.

      import React from "react"
      import { Link, graphql } from "gatsby"
      // import { css } from "@emotion/core"
      // import { rhythm } from "../utils/typography"
      import Layout from "../components/layout"
      
      export default ({ data }) => {
        console.log(data)
        return (
          <Layout>
            <div>
              <h1>
                Amazing Pandas Eating Things
              </h1>
              <h4>{data.allMarkdownRemark.totalCount} Posts</h4>
              {data.allMarkdownRemark.edges.map(({ node }) => (
                <div key={node.id}>
                  <h3>
                    {node.frontmatter.title}{" "}
                    <span>
                      — {node.frontmatter.date}
                    </span>
                  </h3>
                  <p>{node.excerpt}</p>
                </div>
              ))}
            </div>
          </Layout>
        )
      }
      
      export const query = graphql`
        query {
          allMarkdownRemark {
            totalCount
            edges {
              node {
                id
                frontmatter {
                  title
                  date(formatString: "DD MMMM, YYYY")
                }
                excerpt          
              }
            }
          }
        }
      `

      <결과>

      아래와 같이 결과와 내용으로 구성된 리스트가 잘 나타납니다.

       

      6. 마크다운 문서 보이기

      리스트는 나오는데, 각 포스트별 페이지도 없고 링크도 안됩니다. 이 부분을 추가해보도록 하겠습니다.

       

      </src/templates/md-files.js>

      우선 md-files.js라고 템플릿 파일을 하나 생성합니다. GraphQL로부터 값들을 가져와 화면에 뿌려주는 역할을 합니다. 

      import React from "react"
      import { graphql } from "gatsby"
      import Layout from "../components/layout"
      
      export default function Template({ data }) {
        // console.log(data)
        const { markdownRemark } = data
        const { frontmatter, html } = markdownRemark
        return (
          <Layout>
            <div className="blog-post-container">
              <div className="blog-post">
                <h1>{frontmatter.title}</h1>
                <h2>{frontmatter.date}</h2>
                <div dangerouslySetInnerHTML={{ __html: html }} />
              </div>
            </div>
          </Layout>
        )
      }
      
      export const query = graphql`
        query($slug: String!) {
          markdownRemark(fields: { slug: { eq: $slug } }) { 
            html
            frontmatter {
              date(formatString: "MMMM DD, YYYY")        
              title
            }
          }
        }
      `

      markdownRemark(fields: {slug: {eq: $slug} }) 부분은 slug를 키 값으로 하여 각 포스트의 정보를 가져오라는 의미입니다.

       

      <gatsby-node.js>

      page를 생성해주는 템플릿 파일을 만들었으면, 이제 적용해주어야 합니다. 아래와 같이 gatsby-node.js를 작성하여 실행되도록 합니다. createPages는 gatsby에서 페이지를 생성할 때 실행되고, onCreateNode함수는 노드가 생성될 때마다 실행됩니다. onCreateNode 함수에서 마크다운 파일을 읽어 Slug 값을 추출하고, createNodeField 함수를 사용하여 노드에 Slug 값을 추가합니다. 

      const path = require('path');
      
      exports.createPages = ({ graphql, actions }) => {
        const { createPage } = actions;
      
        const blogPostTemplate = path.resolve('src/templates/md-files.js');
      
        return graphql(`
          {
            allMarkdownRemark {
              edges {
                node {
                  fields {
                    slug
                  }
                }
              }
            }
          }
        `).then((result) => {
          if (result.errors) {
            return Promise.reject(result.errors);
          }
      
          result.data.allMarkdownRemark.edges.forEach(({ node }) => {
            createPage({
              path: node.fields.slug,
              component: blogPostTemplate,
              context: {
                slug: node.fields.slug,
              },
            });
          });
        });
      };
      
      exports.onCreateNode = ({ node, getNode, actions }) => {
        const { createNodeField } = actions;
      
        if (node.internal.type === `MarkdownRemark`) {
          const slug = path.basename(node.fileAbsolutePath, '.md');
      
          createNodeField({
            node,
            name: `slug`,
            value: `/${slug}`,
          });
        }
      };

       

      < /src/pages/index.js >

      index.js파일도 링크가 가능하도록 추가 수정해줍니다.

      //~~~
      export default ({ data }) => {
        console.log(data)
        return (
          <Layout>
            <div>
              <h1>
                Amazing Pandas Eating Things
              </h1>
              <h4>{data.allMarkdownRemark.totalCount} Posts</h4>
              {data.allMarkdownRemark.edges.map(({ node }) => (
                <div key={node.id}>
                  <h3>
                    <Link to={node.fields.slug}> <!-- 추가 -->
                      {node.frontmatter.title}{" "}
                      <span>
                        — {node.frontmatter.date}
                      </span>
                    </Link> <!-- 추가 -->
                  </h3>
                  <p>{node.excerpt}</p>
                </div>
              ))}
            </div>
          </Layout>
        )
      }
      
      export const query = graphql`
        query {
          allMarkdownRemark {
            totalCount
            edges {
              node {
                id
                frontmatter {
                  title
                  date(formatString: "DD MMMM, YYYY")
                }
                fields{ <!-- 여기부터 추가 -->
                  slug
                } <!-- 여기까지 -->
                excerpt          
              }
            }
          }
        }
      `

      <결과>

      링크가 가능하도록 변경되었고,,,

       

      클릭하면 개별 페이지 링크로 전환되는 것까지 확인이 됩니다.

      반응형
      반응형

      펠리칸(Pelican) 정적사이트 생성기

        파이썬하면 요즘 ML/DL, 데이터분석 등으로 엄청난 인기를 끌고 있는데요. 웹프로그래밍에서도 매우 많은 인기가 있습니다. 우리나라에서는 점유율이 많이 못미치지만, 해외에서 Django, FastAPI등은 매우 좋은 반응을 보이고 있습니다. 오늘 소개할 도구는 Django나 FastAPI와 같은 서버도구는 아닌, 파이썬으로 만들어진 정적사이트 생성기 펠리칸(Pelican)입니다.

       

      1. 설치

      우선 일반적인 파이썬 프로젝트 시작방법과 마찬가지로 가상환경을 만들어줍니다. (pycharm을 활용하면 쉽게 가상환경이 만들어집니다. VSCode로 진행하면 조금 까다로우니, 다른 포스팅을 참고하세요). 가상환경이 만들어지면 아래의 pip install ~ 로 pelican을 설치합니다. 

      pip install pelican

       

      2. 추가 라이브러리 설치

      github page 배포를 위한 라이브러리 및 마크다운 문서 표시를 위한 추가 라이브러리를 설치합니다.

      pip install ghp-import markdown

       

      3. 프로젝트 생성

      본격적인 프로젝트를 생성합니다. 첫번째 물음에서 [.]으로 되어있으면 현재 사이트에 생성한다는 의미입니다.

      pelican-quickstart

      생성이 완료되면 아래와 같이 프로젝트 구성이 보입니다.

       

      4. 샘플 페이지 작성

      content 폴더에 Hello.md파일을 작성합니다.

      # hello.md #
      Title: Are ‘you’ just inside your skin or is your smartphone part of you?
      Date: 2018-02-26
      Category: Psychology
      Slug: are-you-just-inside-your-skin-or-is-your-smartphone-part-of-you
      
      In November 2017, a gunman entered a church in Sutherland Springs in Texas, where he killed 26 people and wounded 20 others. He escaped in his car, with police and residents in hot pursuit, before losing control of [...]

       

      5. 빌드

      pelican content

       

      6. 실행

      특이하게도 실행은 output폴더로 이동해서 수행해야합니다. 이점은 다른 SSG(Static Site Generator)에 비해 조금 불편하네요. 그리고 일반적인 파이썬 프로젝트와 달리 main.py에서 실행하는게 아니고 pelican.server로 실행해야 합니다. 스크립트를 만들면 되긴 할텐데, 파이썬 공부한지가 오래되어 잘 기억이 안나네요. 

      cd output
      python -m pelican.server

       

      <결과>

      드디어 결과가 나왔습니다. 기본 구성 치고는 여러 요소들을 포함하고 있어 보이는데, 이뻐보이진 않습니다. 손대야 할 부분이 많아보이네요.

      펠리칸(Pelican) 정적사이트 생성기

       

      오늘은 간단히 파이썬 정적사이트 생성기인 펠리칸(pelican)에 대해 간단히 알아봤습니다. 파이썬을 좋아하시는 분들이라면 한번 시도해보셔도 좋을 것 같습니다.

      반응형
      반응형

      이번 포스팅에서는 지난 포스팅에 이어 11ty를 좀더 활용해서 layout을 구성하고, CSS를 적용하는 방법을 알아보도록 하겠습니다.

      1. .eleventy.js파일 생성

      프로젝트 루트에 .eleventy.js 파일을 생성합니다. 여기서 Input / Output 폴더와 CSS 파일의 경로를 지정해줍니다.

      module.exports = function(eleventyConfig) {  
        eleventyConfig.addPassthroughCopy("./src/style.css");
      
        return {
          dir: {
            input: "src",
            includes: "_includes",
            data: "_data",
            output: "_site"
          }
        };
      };

       

      2. 폴더 구성

      폴더구성은 아래와 같이 /src/폴더 하위에 작업중인 파일들을 넣습니다. 주의할 것은 /_includes/ 폴더도 /src/ 하위에 위치해야합니다. 이제 아래의 구성에 따라 layout.njk, index.md, style.css 및 포스트 파일들을 작성하도록 하겠습니다.

       

      3. /src/_includes/layout.njk

      여러 포맷을 지원하지만 예제에서는 nunjucks(njk)파일을 사용합니다. 아래와 같이 프로젝트 전체에서 활용할 layout을 만들어줍니다.

      <!DOCTYPE html>
      <html lang="en">
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <link rel="stylesheet" href="/style.css">
      </head>
      <body>
        <header>
          <h1>{{ title }}</h1>
        </header>
        <main>
          {{ content | safe }}
        </main>
      </body>
      </html>

       

      4. src/index.md

      메인 페이지에서 사용하는 레이아웃도 위에서 만든 layout.njk입니다. 그러나 내부에 보여줄 컨텐츠가 각 포스트들의 타이틀과 컨텐츠의 목록입니다.

      ---
      layout: "layout.njk"
      title: The Best Eleventy Demo
      ---
      
      {% for post in collections.post %}
      <h2><a href="{{ post.url }}">{{ post.data.title}}</a></h2>
      <p>{{ post.content }}</p>
      {% endfor %}

       

      5. src/style.css

      CSS파일도 적당히 구성하여 /src폴더에 넣어줍니다.

      *{
        font-family:Georgia, 'Times New Roman', Times, serif;
      }
      
      header {  
        position: relative;      
        text-align: center;
        font-size: x-large;
      }
      
      main {
        position: relative;    
        margin-top: 50px;
        font-size:15px;
        text-align: center;
      }
      a {
        text-decoration: none;
      }

       

      6. src/posts/

      # post1.md (아래의 형식으로 몇개 더 만들어놓습니다.)

      ---
      title: Post Title
      ---
      asdfxvzxcv

      # posts.json

      posts폴더 내부의 파일들이 사용할 레이아웃과, 각 파일들의 태그 정보를 설정해줍니다. 여기서는 "tags"값으로 "post"를 지정하였습니다. 이 값은 collections라는 변수로 사용할 수 있습니다. 위의 index.md에서 보시듯이 {{ collections.post }}와 같이 값을 불러와 사용합니다.

      {
        "layout": "layout.njk",
        "tags": "post"
      }

       

      <결과>

       

      아직 여러가지 plugin (검색창, 댓글 등)을 확인해보지는 않았지만, 비교적 단순하게 구축이 가능한 정적사이트 생성기로 보입니다. 

      반응형
      반응형

      정적사이트 생성기가 여러개다보니 이것저것 시도해봅니다. 그중에 이번엔 11ty라는 정적사이트 생성기에 대해 알아보겠습니다. 사용법이 매우 쉽습니다.

       

      [ 목차 ]

         

        1. 설치

        프로젝트로 쓸 폴더를 생성 후 11ty를 깔아줍니다. (기본적으로 Node는 깔려있어야 합니다.)

        npm init -y
        npm install @11ty/eleventy --save-dev

         

        2. 샘플 파일 생성

        2가지 파일을 생성해야합니다. 리눅스라면 아래의 명령어로 생성이 가능한데, 윈도우 환경이면 직접 두 파일을 만듭니다.

        # index.html
        echo '<!doctype html><title>Page title</title><p>Hi</p>' > index.html
        #README.md
        echo '# Page header' > README.md

         

        3. 서버 실행

        npx @11ty/eleventy --serve
        
        or
        
        npx eleventy --serve

        이렇게만 작성하고 실행해도 index.html을 확인할 수 있습니다. 다른 방법으로는 기존의 방식대로 package.json에 script를 작성해서 사용해도 됩니다.

        {
          "scripts":{
            "dev":"eleventy --serve",
            "build":"eleventy"
          },
          "devDependencies": {
            "@11ty/eleventy": "^2.0.1"
          }
        }

         

        4. post 작성

        기초적인 사용법을 알아볼 겸 이제부터 프로젝트에 조금씩 살을 붙이도록 하겠습니다. 오늘 구성할 프로젝트 구조는 아래와 같습니다. 

        우선 샘플 포스트를 아래와 같이 작성합니다.

        # post1.md
        
        ---
        title: Post Title
        ---
        asdfxvzxcv

         

        # post2.md
        
        ---
        title: My second post
        ---
        zxcvgdfg2343

         

        5. 레이아웃 파일 생성

        /_includes/layout.html파일을 생성합니다. 이 레이아웃으로 모든 md파일의 title과 content를 표시할 예정입니다.

        <!DOCTYPE html>
        <html lang="en">
        <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Document</title>
        </head>
        <body>
          <header>
            <h1>{{ title | default: "My Blog" }}</h1>
          </header>
          <main>
            {{ content }}
          </main>
        </body>
        </html>

         

        그리고 /posts/posts.json파일을 아래와 같이 작성합니다.

        {
          "layout": "layout.html",
          "tags": "post"
        }

        layout은 layout.html파일을 이용하겠다는 뜻이고, post라는 태그가 들어있는 파일들의 리스트를 사용하겠다는 의미입니다.

         

        또한 index.html파일은 layout을 이용해 표시할 예정이므로 아무 내용 없이 layout만 지정합니다.

        <!-- index.html -->
        ---
        layout: layout.html
        ---

         

        이렇게 작성하고 아래의 url에서 잘 표시되는지 확인해 봅시다.

        [http://localhost:8080/posts/post1/]

         

        [http://localhost:8080/posts/post2/]

        레이아웃이 잘 적용되었죠?

         

        6. 포스트 리스트

        마지막으로 메인화면에 포스트 목록을 보여주도록 하겠습니다. 이때 태그로 지정했던 post를 이용해서 리스트를 가져옵니다. index.html을 아래와 같이 수정해줍니다.

        <!-- index.html -->
        ---
        layout: layout.html
        ---
        
        {% for post in collections.post %}
        <h2><a href="{{ post.url }}">{{ post.data.title}}</a></h2>
        <p>{{ post.content }}</p>
        {% endfor %}

         


        한번 사용해본 바로는, 이 기능만 있어도 될만큼 엄청 쉽네요. 나머진 CSS로 꾸미기만 해도 될 것 같습니다. 엄청 빠르고 쉬운 11ty. 한번 사용해볼만 한 것 같습니다. 

        반응형
        반응형

        Static Site Generator

        [ 목차 ]

          오늘은 정적사이트 생성기 (SSG, Static Site Generator)에 대해 알아보겠습니다.

           

          1. 정적사이트(SSG) 생성기란?

          정적사이트 생성기는 말 그대로 정적 사이트를 생성하는 도구를 말하는데요, 정적 사이트란 항상 동일한 내용을 보여주는 사전에 빌드된 HTML, Javascript 및 CSS 코드로 구성된 사이트를 일컫는 말입니다. 이러한 웹페이지는 이미 서버 측에서 미리 렌더링된 후 브라우저에 제공되기 때문에 페이지를 더 빠르게 로드할 수 있습니다. 그래서 개인 블로그 등 컨텐츠 변화가 자주 일어나지 않는 소규모 웹사이트 제작시 많이 이용되고 있습니다. 대표적인 도구로는 Jekyll, Gatsby, Hugo 등이 있습니다.

           

            이것만 알면 뭔가 부족한거 같아서 몇가지 개념을 좀 더 알아보겠습니다. 정적 사이트 생성기가 있으면 동적 사이트 생성기도 있을까요? 비슷한 의미로 쓰이는 SSR이라는 개념이 있습니다. SSR은 Server Side Rendering의 약자인데 말 그대로 클라이언트로부터 요청이 들어오면(들어올 때마다) 실시간으로 서버에서 웹페이지를 만들어내는 기술입니다. 대표적으로는 React 기반의 Next.js와 Vue.js 기반의 Nuxt.js, 그리고 Svelte 기반의 SvelteKit이 있습니다. React, Vue.js, Svelte는 SPA라고 합니다. SPA는 Single Page Application의 약자로 우리말로 하면 단일페이지 어플리케이션이 되겠습니다. 웹사이트의 전체 페이지를 하나의 페이지에 담아 클라이언트측에 내려주고, 클라이언트의 반응에 따라 동적으로 화면(데이터)을 변경하는 기술을 말합니다. SSR과는 달리 페이지를 클라이언트측에서 모두 처리하죠. 

           

           

          2. 정적 사이트 생성기의 종류

          정적 사이트 생성기는 아래 그림에서 보이듯이 매우 다양합니다. Next.js와 .Nuxt.js는 SSR기술이라고 설명드렸는데, 주 용도는 SSR이지만 SSG로 사용할 수도 있는 옵션(방법)을 제공합니다.

           

          - React 기반: Next.js, Gatsby, Docusaurus

          - Vue.js 기반: Nuxt.js, Vuepress, Gridsome

          - Javascript 기반: Hexo, Docsify, Eleventy

          - Go 기반 : Hugo

          - Ruby on Rails 기반 : Jekyll

          - Python 기반 : pelican

           

          [ 정적사이트 생성기 Github Star Ranking ]

          Static Site Generator

          저도 SSG를 React, Vue.js 등을 공부하며 알게된 터라 Gatsby 그리고 오래전부터 쓰인 Jekyll정도 알고 있었는데, 정말 다양한 SSG가 존재하네요. 그리고 Go언어로 작성된 Hugo의 인기가 굉장한 것 같아요. 속도 하면 역시 Go언어를 따라올 만한게 별로 없을 것 같네요.

           

          3. 정적 사이트 생성기 (SSG) 테마

          정적사이트 생성기는 다양한 테마를 적용가능한 장점도 있습니다. 이 테마를 이용한 Starter Kit을 통째로 설치해서 쉽게 시작할 수 있죠. 다양한 SSG의 테마를 한곳에서 확인할 수 있는 사이트에 대해 알려드리려고 합니다.

           

          Jamstack Themes

           

          Jamstack Themes

          Over 1000 themes and starters for Jamstack and static site generators.

          jamstackthemes.dev

          위 사이트에서는 각종 정적사이트 생성기(SSG, Static Site Generator)에서 사용 가능한 테마를 보여줍니다. 해당 사이트로 이동하여 상단에 All Themes를 클릭하세요. 전체 테마 리스트를 보여줍니다. 

          Static Site Generator Theme

          테마의 수로는 Jekyll > Hugo > Gatsby > Next.. 순입니다. Github Start 랭킹과도 통하는게 있네요. 오래전부터 쓰이던 Jekyll이 테마가 제일 많고, Go언어의 Hugo가 역시 엄청난 인기에 힘입어 테마도 많다고 판단해야겠죠? 뒤이어 Gatsby가 나오네요. 제가 좋아하는 vue관련된 SSG (Vuepress, Gridsome)의 테마는 거의 없다고 봐도 무방하겠네요^^;; (바꿔야되나..) 다양한 테마의 github 레포지토리를 여러분의 레포지토리에 복사 (또는 Netlify에 배포하면서 동시 복사)해서 사용해보시기 바랍니다.

           

          분이라면 어떤게 좋을 것 같나요? 이쁜 테마를 골라서 Netlify나 Github 등에 배포하고 나만의 멋진 블로그를 만들어보세요.^^

           

          그럼 이만~~

           

           

          반응형
          반응형

          파이썬으로 엑셀 파일을 불러오고 데이터 다루는 가장 기본적인 라이브러리인 openpyxl에 대해 알아보도록 하겠습니다.

          [ 목차 ]

            1. Openpyxl 설치

            $ pip install openpyxl

             

            2. 파일 생성

            import openpyxl
            
            # 새로운 엑셀 파일 생성
            wb = openpyxl.Workbook()
            
            # 새로운 시트 추가
            sheet = wb.active
            
            # 셀에 데이터 쓰기
            sheet['A1'] = '가나다'
            sheet['B1'] = '라마바사아'
            
            # 엑셀 파일로 저장
            wb.save('example.xlsx')

             

            3. 파일 열기

            import openpyxl
            # 기존 파일 열기
            wb=openpyxl.load_workbook('example.xlsx') #파일명 혹은 패스+파일명 입력
            
            # sheet 선택하기
            sheet = wb.get_sheet_by_name('Sheet1')
            
            # 셀 값 가져오기 방법1
            sheet['A1'].value 
            # 셀 값 가져오기 방법2
            sheet.cell(row=1, column=1).value

             

            4. 워크시트 추가

            import openpyxl
            wb = openpyxl.Workbook()
            
            wb.create_sheet() # 기본값으로 시트 생성
            wb.create_sheet(index=숫자, title=’시트명’) # 지정된 이름으로 시트 생성

             

            5. 행/열 다루기

            모든 행/열 출력

            from openpyxl import load_workbook
            import pandas as pd
            
            wb = load_workbook('sample.xlsx')
            ws = wb['sample']
            
            all_values = []
            for row in ws.rows:
              for cell in row:
                all_values.append(cell.value)
            
            print(all_values)

             

            6. Pandas Dataframe으로 데이터 변환

            from openpyxl import load_workbook
            import pandas as pd
            
            wb = load_workbook('sample.xlsx')
            ws = wb['sample']
            
            data = ws.values
             
            columns = next(data)[0:]  #첫행을 칼럼으로 쓰고, 첫행은 data에서 지워짐
            
            df = pd.DataFrame(data, columns=columns)

             

            기본적인 내용만 정리해 올립니다.

            반응형

            + Recent posts