Org-mode 문서에서 링크를 긁어서 링크 섹션을 만들기
문장에 걸어놓은 url 링크가 많으면 찾아보기 쉽게 링크 섹션을 하나 만들어서 모아두고 싶다. 링크가 많은 프로그래밍 관련 글에서 주로 이렇게 하고 있는데, org-mode로 글을 쓰면서 왜 이걸 손으로 하고 있나 자괴감이 든다. 그래서 글에서 http https url을 싹 긁고 해당 url을 방문해서 타이틀을 가져와 링크 리스트를 만드는 emacs lisp 함수를 만들었다.
(defun my-org-extract-urls (org-elements)
;; link 타입 org element만 map
(org-element-map org-elements 'link
(lambda (link)
(let* ((link-part (nth 1 link))
(type (plist-get link-part :type))
(path (url-unhex-string (plist-get link-part :raw-link))))
;; "https", "http"로 시작하는 link만 골라낸다
(if (or (string= type "https") (string= type "http"))
;; "https://...", "http://..." 같은 전체 주소
path)))))
org 요소(element)들을 받아 https, http로 시작하는 url만 추출한다. 정규식을 써서 link를 골라내야 하나 계획을 세우고 있었는데, 유용하게 쓸 수 있는 org 요소 API가 많다. org-element-map
함수를 사용해 org 요소에서 url을 추출한다. org mode 다른 함수를 만들 때, 사용한 함수 같다. 잘 정리해서 유저가 사용할 수 있는 API로 제공한다.
url-unhex-string
함수를 사용해 퍼센트 인코딩을 디코딩해서 저장한다. 퍼센트 인코딩된 문자열을 그냥 저장하니 제대로 저장되지 않아서 디코딩해서 저장했다.
(my-org-extract-urls (org-element-parse-buffer))
org mode 버퍼 전체에서 url을 추출하고 싶다면 org-element-parse-buffer
함수를 인자로 넘기면 된다.
(defun my-org-build-link-section ()
(interactive)
(let ((links (my-org-extract-urls (org-element-parse-buffer))))
(seq-do (lambda (link) (message link)) links)))
M-x my-org-build-link-section
명령으로 실행할 수 있는 대화형 함수를 만들어보자. 위에서 만든 my-org-extract-urls
함수를 호출해서 지금 편집 중인 버퍼에 있는 url을 모두 추출해 message 버퍼에 찍게 했다. 잘 나온다.
잘 추출하는 걸 확인했으니 이제 heading을 만들고 url을 목록으로 추가해본다.
(defun my-org-build-link-section ()
(interactive)
(let ((links (my-org-extract-urls (org-element-parse-buffer))))
(org-insert-heading-after-current)
(insert "링크")
(org-return t)
(org-return t)
(seq-map-indexed (lambda (elt idx)
;; 첫번째 요소는 직접 정렬되지 않은 목록 아이템을 넣어준다
(if (= idx 0)
(progn
(insert (format "- %s" elt))
(org-return t))
;; 두번째 요소 부터는 org-insert-item 함수를 호출해
;; 이전 목록 아이템을 참고해 자동으로 넣는다
(progn
(org-insert-item)
(insert elt)
(org-return t))
))
links)))
M-return
과 비슷하게 동작하는 org-insert-heading-after-current
함수를 호출해서 heading을 만들었다. 어디에 heading을 추가할지 고민했는데, 커서가 있는 위치 근처에 추가해야 놀라지 않을 것 같아서 이렇게 결정했다.
이제 목록을 추가할 차례다. org-list-insert-item
함수를 사용하려고 했는데, 왜 이렇게 복잡하냐? 우아하게 추가하는 건 나중으로 미룬다. 첫번째 항목을 (insert (format "- %s" elt))
이런 식으로 -
문자를 앞에 붙여서 추가한다. org mode에서 목록으로 인식한다. 그다음 항목부터는 (org-insert-item)
함수를 호출해서 org mode에서 다음 항목을 추가할 준비를 대신하게 한다.
이제 막바지다. url을 방문해서 제목을 가져온다. 이 정보로 link를 만든다. ’org-cliplink 패키지로 title과 url을 편하게 삽입’ 글에서 추가한 org-cliplink
패키지 함수에 url을 넘기면 link를 삽입해준다. 이 패키지를 이용하자.
(defun my-org-build-link-section ()
(interactive)
(let ((links (sort
(delete-dups (my-org-extract-urls (org-element-parse-buffer)))
'string<)))
(org-insert-heading-after-current)
(insert "링크")
(org-return t)
(org-return t)
(seq-map-indexed (lambda (elt idx)
(message (format "processing - %s" elt))
(let* ((url (url-encode-url elt))
(title (or (org-cliplink-retrieve-title-synchronously url)
"nil"))
(link-elt (my-org-link-transformer url title)))
;; 첫번째 요소는 직접 정렬되지 않은 목록 아이템을 넣어준다
(if (= idx 0)
(progn
(insert (format "- %s" link-elt))
(org-return t))
;; 두번째 요소 부터는 org-insert-item 함수를 호출해
;; 이전 목록 아이템을 참고해 자동으로 넣는다
(progn
(org-insert-item)
(insert link-elt)
(org-return t)))))
links)))
글에 같은 링크가 여러 번 들어갈 수 있다. delete-dups
함수를 호출해 중복을 없애고 비슷한 링크가 뭉칠 수 있게 sort
함수로 정렬한다. url-encode-url
함수를 사용해 퍼센트 인코딩한다. org-cliplink-retrieve-title-synchronously
함수를 호출해서 title을 가져온다.
(defun my-org-link-transformer (url title)
(let* ((parsed-url (url-generic-parse-url url)) ;parse the url
(host-url (replace-regexp-in-string "^www\\." "" (url-host parsed-url)))
(clean-title
(cond
;; if the host is github.com, cleanup the title
((string= (url-host parsed-url) "github.com")
(replace-regexp-in-string "^/" ""
(car (url-path-and-query parsed-url))))
;; (replace-regexp-in-string "GitHub - .*: \\(.*\\)" "\\1" title))
((string= (url-host parsed-url) "www.youtube.com")
(replace-regexp-in-string "\\(.*\\) - Youtube" "\\1" title))
;; otherwise keep the original title
(t title)))
(title-with-url (format "%s - %s" clean-title host-url)))
;; forward the title to the default org-cliplink transformer
(org-cliplink-org-mode-link-transformer url title-with-url)))
my-org-link-transformer
함수에서 url과 title을 받아서 org mode에서 link로 인식할 수 있게 [[LINK][DESCRIPTION]]
문자열을 만든다. ’org-cliplink 패키지로 title과 url을 편하게 삽입’ 글에서 추가한 my-org-cliplink
함수에서 추가한 후처리 코드를 my-org-link-transformer
함수로 추출했다.
전체 코드
(defun my-org-build-link-section ()
(interactive)
(let ((links (sort
(delete-dups (my-org-extract-urls (org-element-parse-buffer)))
'string<)))
(org-insert-heading-after-current)
(insert "링크")
(org-return t)
(org-return t)
(seq-map-indexed (lambda (elt idx)
(message (format "processing - %s" elt))
(let* ((url (url-encode-url elt))
(title (or (org-cliplink-retrieve-title-synchronously url)
"nil"))
(link-elt (my-org-link-transformer url title)))
;; 첫번째 요소는 직접 정렬되지 않은 목록 아이템을 넣어준다
(if (= idx 0)
(progn
(insert (format "- %s" link-elt))
(org-return t))
;; 두번째 요소 부터는 org-insert-item 함수를 호출해
;; 이전 목록 아이템을 참고해 자동으로 넣는다
(progn
(org-insert-item)
(insert link-elt)
(org-return t)))))
links)))
(defun my-org-link-transformer (url title)
(let* ((parsed-url (url-generic-parse-url url)) ;parse the url
(host-url (replace-regexp-in-string "^www\\." "" (url-host parsed-url)))
(clean-title
(cond
;; if the host is github.com, cleanup the title
((string= (url-host parsed-url) "github.com")
(replace-regexp-in-string "^/" ""
(car (url-path-and-query parsed-url))))
;; (replace-regexp-in-string "GitHub - .*: \\(.*\\)" "\\1" title))
((string= (url-host parsed-url) "www.youtube.com")
(replace-regexp-in-string "\\(.*\\) - Youtube" "\\1" title))
;; otherwise keep the original title
(t title)))
(title-with-url (format "%s - %s" clean-title host-url)))
;; forward the title to the default org-cliplink transformer
(org-cliplink-org-mode-link-transformer url title-with-url)))
(defun my-org-extract-urls (org-elements)
;; link 타입 org element만 map
(org-element-map org-elements 'link
(lambda (link)
(let* ((link-part (nth 1 link))
(type (plist-get link-part :type))
(path (url-unhex-string (plist-get link-part :raw-link))))
;; "https", "http"로 시작하는 link만 골라낸다
(if (or (string= type "https") (string= type "http"))
;; "https://...", "http://..." 같은 전체 주소
path)))))
참고
- 2.2 URI Encoding - gnu.org
- 6.1 Sequences - gnu.org
- Org Element API - orgmode.org
- 원하는 URL을 조합해 웹브라우저로 여는 대화형 함수 만들기 - elixir 문서 편하게 검색 - ohyecloudy.com
- org-cliplink 패키지로 title과 url을 편하게 삽입 - ohyecloudy.com
- 퍼센트 인코딩 - ko.wikipedia.org
C-x C-s C-x C-c