soma0sd

코딩 & 과학 & 교육

[VSCODE] 티스토리 스킨 개발 세팅: 컴파일러 제작

반응형

요즘 만들어둔 스킨이 맘에 안들 때 문제있는 부분을 찾아서 만들기가 너무 어려워 아예 처음부터 다시 만드는 경우가 많습니다. 때문에 유지 관리하기 편한 개발환경을 만들기 위한 고민을 시작했습니다.

개발 언어와 기능

기본적으로 티스토리 스킨에는 HTML, CSS, javascript를 사용할 수 있습니다. 이들을 그대로 사용하다보니 수정할거리가 생기면 찾기가 너무 힘들었는데요. 컴파일 과정을 거쳐야 하지만 유지관리에는 훨씬 유리한 SASS(SCSS), Typescript를 사용하기로 했습니다. 추가로 HTML파일에는 import태그를 추가해서 테마를 조각단위로 작성한 후, 컴파일을 통해서 하나의 skin.html로 만드는 기능을 추가하기로 했습니다.

Python 패키지

자주 사용하는 언어가 파이썬(python)이다보니 node.js를 배워서 vscode용 확장기능을 만들기보다는 그냥 파이썬으로 해본 다음에 고민해보기로 했습니다. 추가로 설치가 필요한 패키지는 다음과 같습니다.

이 패키지들은 아래 명령으로 설치할 수 있습니다.

pip install libsass
pip install beautifulsoup4
pip install watchdog

사용법

아래 단락의 소스코드를 {프로젝트 루트}/compiler/tt_watch.py로 저장한경우를 기준으로 작성합니다.

tasks.json

.vscode/tasks.json은 해당 프로젝트에서 실행할 명령이나 파일작업을 테스크 단위로 실행하고자 할때 사용합니다. 아래 예시문은 제작한 스킨 컴파일러와 타입스크립트 컴파일러를 묶어서 빌드 테스크로 만들어 둔 것입니다.

Ctrl+Shift+B를 눌러 빌드테스크를 한번에 실행할 수 있습니다.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Tistory_Watch",
      "type": "shell",
      "command": "python ./compiler/tt_watch.py"
    },
    {
      "label": "typescript_Watch",
      "type": "typescript",
      "tsconfig": "tsconfig.json",
      "option": "watch",
      "problemMatcher": [
        "$tsc-watch"
      ]
    },
    {
      "label": "Build",
      "dependsOn": [
        "typescript_Watch",
        "Tistory_Watch"
      ],
      "group": {
        "kind": "build",
        "isDefault": true
      }
    }
  ]
}

tt_config.json

제작한 스킨 컴파일러의 속성을 설정하는 파일입니다. 이 설정파일이 없는 경우 컴파일러는 기본값으로 채워진 설정파일을 만듭니다. 기본값은 아래와 같습니다.

{
  "out_dir": "dist",
  "html": {
    "dir": "_html",
    "main": "main.html",
    "out": "skin.html"
  },
  "sass": {
    "dir": "_sass",
    "main": "main.scss",
    "out": "theme.min.css"
  }
}
  • "out_dir"출력파일의 디렉토리
  • "html", "sass": 언어 설정
    • "dir": 작업 디렉토리
    • "main": 컴파일 대상 파일의 이름
    • "out": 출력할 파일의 이름

html including 기능

{% include "{스킨 조각}.html" %}을 사용해 해당 위치에 스킨 조각파일의 내용을 불러옵니다.

_html/main.html

<html>
  <head></head>
  <body>
    {% include "body.html"%}
  </body>
</html>

_html/body.html

<div>body</div>

위 파일을 컴파일 한 결과는 다음과 같습니다.

dist/skin.html

<html>
  <head></head>
  <body>
    <div>body</div>
  </body>
</html>

소스코드

import time, re, json, os
import sass
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from bs4 import BeautifulSoup


default_config = {
  "out_dir": "dist",
  "sass": {
    "dir": "_sass",
    "main": "main.scss",
    "out": "theme.min.css"
  },
  "html": {
    "dir": "_html",
    "main": "main.html",
    "out": "skin.html"
  }
}
config_file_name = "tt_config.json"
include_tag_rex = r'\{\%[ ]*?include[ ]+?\"(?P<src>.+?)\"[ ]*?\%\}'


class EventHandler(FileSystemEventHandler):
  def on_any_event(self, event):

    with open(config_file_name, 'r', encoding="utf8") as f:
      config.update(json.loads(f.read()))

    out_dir  = os.path.join('./', config['out_dir'])
    out_sass = os.path.join(out_dir, config['sass']['out'])
    out_html = os.path.join(out_dir, config['html']['out'])

    base_sass = os.path.join('./', config['sass']['dir'])
    base_html = os.path.join('./', config['html']['dir'])
    main_sass = os.path.join(base_sass, config['sass']['main'])
    main_html = os.path.join(base_html, config['html']['main'])

    if (re.search(config['out_dir'], event.src_path)):
      return 0
    print(f'{event.event_type} : {event.src_path}')
    if (not os.path.exists(out_dir)):
      os.mkdir(out_dir)
    try:
      sass_str = sass.compile(
        filename=main_sass, output_style='compressed',include_paths=base_sass)
    except:
      sass_str = ''

    if (sass_str):
      with open(out_sass, 'w', encoding='utf8') as file_out_sass:
        file_out_sass.write(sass_str)
    if (os.path.exists(main_html)):
      with open(main_html, 'r', encoding='utf8') as file_base_html:
        html_str = file_base_html.read()
      m = re.search(include_tag_rex, html_str)
      while m:
        html_str = html_str.replace(
          m.group(), self._get_html_part(base_html, m.group('src')))
        m = re.search(include_tag_rex, html_str)
      with open(out_html, 'w', encoding='utf8') as f:
        soup = BeautifulSoup(html_str, 'html.parser')
        f.write(soup.prettify())

  def _get_html_part(self, root, src):
    try:
      with open(root + src, 'r', encoding='utf8') as f:
        return f.read()
    except:
      print(f'not exist: {root + src}')
      return ''


if __name__ == "__main__":
  config = default_config
  if(os.path.exists(config_file_name)):
    with open(config_file_name, 'r', encoding="utf8") as f:
      config.update(json.loads(f.read()))
  else:
    with open(config_file_name, 'w', encoding="utf8") as outfile:
      json.dump(default_config, outfile, sort_keys=True,
                indent=2, separators=(',', ': '))

  event_handler = EventHandler()
  observer = Observer()
  observer.schedule(event_handler, path="./", recursive=True)
  observer.start()

  try:
    while True:
      time.sleep(1)
  except KeyboardInterrupt:
    observer.stop()
  observer.join()
반응형
태그:

댓글

End of content

No more pages to load