#TIL git patch를 적용하는 apply 명령과 am 명령

GitHub에 있는 커밋을 내 저장소에 적용하고 싶다. 리모트 저장소로 등록을 안 한 상태라서 git cherry-pick 명령은 사용하지 못한다. 어떻게 하면 될까?

프로젝트를 운영하는 것은 크게 두 가지로 이루어진다. 하나는 format-patch 명령으로 생성한 Patch를 이메일로 받아서 프로젝트에 Patch를 적용하는 것이다.

Git - 프로젝트 관리하기 - git-scm.com

patch를 받아서 적용하면 된다. GitHub은 커밋 URL 끝에 .patch 를 붙이면 format-patch 명령으로 만든 형식으로 커밋을 보여주는 기능을 제공한다.

GitHub Doom Emacs 저장소에 있는 9620bb45ac4c 커밋을 적용해 보자

$ curl https://github.com/doomemacs/doomemacs/commit/9620bb45ac4cd7b0274c497b2d9d93c4ad9364ee.patch | git apply --verbose
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1187  100  1187    0     0   1916      0 --:--:-- --:--:-- --:--:--  1917
Checking patch modules/editor/evil/autoload/unimpaired.el...
Applied patch modules/editor/evil/autoload/unimpaired.el cleanly.

git diff 명령으로 patch가 적용됐는지 확인한다.

$ git diff
diff --git a/modules/editor/evil/autoload/unimpaired.el b/modules/editor/evil/autoload/unimpaired.el
index 0e64d427b..b698b56ea 100644
--- a/modules/editor/evil/autoload/unimpaired.el
+++ b/modules/editor/evil/autoload/unimpaired.el
@@ -113,7 +113,7 @@ See `+evil/next-preproc-directive' for details."
     (user-error "Must be called from a file-visiting buffer"))
   (let* ((directory (file-name-directory buffer-file-name))
          (filename (file-name-nondirectory buffer-file-name))
-         (files (cl-remove-if #'file-directory-p (doom-glob (file-name-directory buffer-file-name) "[!.]*")))
+         (files (cl-remove-if-not #'file-regular-p (doom-glob (file-name-directory buffer-file-name) "[!.]*")))
          (index (cl-position filename files :test #'file-equal-p)))
     (when (null index)
       (user-error "Couldn't find this file in current directory"))

잘 된다. 하지만 기여자의 이름이라든지 커밋 메시지는 적용되지 않는다. diff만 적용된다. git format-patch 명령으로 만들면 커밋 메시지를 포함해 기여자의 정보까지 다 기록되어 있다. 이것까지 적용하려면 어떻게 하면 될까?

format-patch 명령으로 생성한 Patch 파일은 git am 명령으로 적용한다(am 은 “apply a series of patches from a mailbox”의 약자다). git am 은 mbox 파일을 읽어 그 안에 이메일 메시지가 한 개가 있든 여러 개가 있든 처리할 수 있다.

Git - 프로젝트 관리하기 - git-scm.com

git am 명령을 사용하면 된다. 이 명령으로 다시 적용해보자.

$ curl https://github.com/doomemacs/doomemacs/commit/9620bb45ac4cd7b0274c497b2d9d93c4ad9364ee.patch | git am
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  1187  100  1187    0     0   2026      0 --:--:-- --:--:-- --:--:--  2029
Applying: fix(evil): ]f/[f opening broken symlinks

git apply 명령과 다르게 바로 커밋을 만들어준다.

$ git log -n 1
commit e632775ed333e8ed2088287c2de519463e697c87 (HEAD -> master)
Author: Henrik Lissner <git@henrik.io>
Date:   Thu Apr 18 13:50:28 2024 -0400

    fix(evil): ]f/[f opening broken symlinks

diff외 정보도 잘 보존된다.

참고


authorized_keys 파일을 직접 수정하지 말고 ssh-copy-id 프로그램 사용

공개키를 접속하려는 서버 authorized_keys 파일에 추가하면 편하다. 패스워드 없이 접속할 수 있기 때문이다.

이제까지 난 무식하게 추가했다. 클립보드에 현재 머신의 공개키를 복사한다.

pbcopy < ~/.ssh/id_ed25519.pub

그다음엔 원격 서버에 접속한다. 이때는 ID와 패스워드를 사용해서 접속한다. 접속한 다음 클립보드에 복사한 공개키를 authorized_keys 파일 끝에 추가한다.

ssh user@host
vi ~/.ssh/authorized_keys

ssh-copy-id 프로그램의 존재를 몰랐다. 알고 나니 억울하다. ssh-copy-id 프로그램을 사용하면 공개키를 서버의 authorized_keys 파일에 쉽게 추가할 수 있다.

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@host

클립보드에 복사하고 접속하고 파일 열어서 붙여 넣고 이럴 필요가 없다. ssh-copy-id 프로그램을 사용하면 편하게 추가할 수 있다.

참고


#TIL #elixirlang 생성기를 조합할 때는 StreamData.bind/2

StreamData는 값을 생성하는 Elixir 라이브러리다. Property-based testing을 할 때, 주로 사용하고 있다.

유저 정보를 외부에서 읽는 걸 테스트한다고 치자. 0에서 10명의 정보를 읽고 이 정보 안에서 이상한 데이터가 포함된 걸 테스트하고 싶다. 이상한 데이터 수는 유저 데이터 수보다 같거나 적어야 한다.

즉, [0, 10] 범위에서 랜덤한 수를 하나 뽑고 [0, 뽑은 수] 사이에서 하나를 더 뽑아 이상한 데이터 수를 만들어야 한다. 이렇게 생성기(generator)를 조합하고 싶을 때, StreamData.bind/2 함수를 사용할 수 있다.

user_count, invalid_user_count 생성기를 정의해본다.

user_count = StreamData.integer(0..10)
invalid_user_count = StreamData.bind(user_count, fn c -> StreamData.integer(0..c) end)
user_count |> Enum.take(1)
#=> [4]
invalid_user_count |> Enum.take(1)
#=> [5]

의도와 다르게 invalid_user_count가 user_count 보다 크다. StreamData가 골라낸 랜덤한 수를 사용해야 하는데, [0, 10] 범위의 수 중에 하나를 생성하는 생성기 자체를 바인딩해서 그렇다. 즉, user_data도 [0, 10] 사이 수를 하나 생성하고 invalid_user_count는 [0, 10] 사이 수를 생성하고 다시 [0, 생성한 수] 사이에서 하나를 생성하기 때문이다.

StreamData.bind/2 함수에서 user_count와 invalid_user_count를 둘 다 생성하게 고쳐보자.

data = StreamData.bind(
StreamData.integer(0..10),
fn data -> {StreamData.constant(data), StreamData.integer(0..data)} end)

[0, 10] 사이 수를 생성한다. 튜플(tuple) 첫 번째 요소로 생성한 수 자체를 사용하고 두 번째 요소로는 [0, 생성한 수] 사이 수를 생성하게 고친다.

data |> Enum.take(1)
#=> [{3, 0}]
data |> Enum.take(1)
#=> [{8, 6}]
data |> Enum.take(1)
#=> [{9, 4}]

의도한대로 동작한다. 생성한 값이 아니라 생성기 자체를 StreamData.bind/2 함수에 넘겨서 다시 한번 더 생성하게 해서 의도한 대로 동작하지 않았다.


#TIL 리스트 요소(element)를 원하는 개수만큼 생성하는 함수

build_list 라는 함수를 짜고 있다. 요소(element)를 원하는 개수만큼 생성하게 하고 싶다.

def build_list(count, elem_func) do
  for _ <- 1..count do
    elem_func.()
  end
end

Range를 사용해서 [1, 2, ... count] 를 만들고 elem_func 함수를 호출해서 리스트를 만들 수 있다. 하지만 0개를 만들려면 문제가 된다.

1..0 |> Enum.to_list()
[1, 0]

count 가 0이면 Range에서 인자로 넘기지 않으면 자동으로 계산해 주는 step이 -1 이 되면서 1과 0에 대해 만들 게 된다. 결과 리스트 사이즈가 0이 되길 원했는데, 2가 된다.

그렇다면 count 에 대해 분기를 타볼까?

def build_list(count, elem_func) do
  if count > 0 do
    for _ <- 1..count do
      elem_func.()
    end
  else
    []
  end
end

원하는 대로 동작하지만 뭔가 더 나은 방법이 반드시 있을 것 같은 구현이다.

그렇다. Stream.repeatedly를 사용하면 된다.

def build_list(count, elem_func) do
  Stream.repeatedly(fn -> elem_func.() end)
  |> Enum.take(count)
end

무한히 생성하는 Stream을 만들고 count만큼 가져온다. Enum.take/2 함수는 두 번째 인자로 0을 넘기면 빈 문자열을 리턴한다. 원하던 함수 구현이다. ExMachina.build_list/4 소스 코드에서 배웠다.


#TIL #elixirlang mix.lock에 있지만 사용하지 않는 의존성 검사

mix.exs 파일을 수정해 의존성을 추가해서 라이브러리를 사용하다가 mix.exs 파일에서만 의존성을 제거한다. mix.lock 파일에서도 지워야하는데, 그대로 놔뒀다. 이제 mix.lock 에 있어서 다운로드하지만 사용하지 않는 쓰레기 의존성이 생겼다.

사용하지도 않는데, 구태여 받을 필요가 없다. 그런 의존성이 있는지는 deps.unlock mix 태스크(task)에 옵션을 주면 검사할 수 있다.

mix deps.unlock --check-unused

mix.lock 파일에 사용하지 않는 의존성이 있는지 검사한다. exit code로 사용하지 않는 의존성이 지금 있는지 확인할 수 있다. 나는 script/test 스크립트에 추가했다.

테스트 방법

$ git diff
diff --git a/apps/builder/mix.exs b/apps/builder/mix.exs
index 37eee14..c1b05a7 100644
--- a/apps/builder/mix.exs
+++ b/apps/builder/mix.exs
@@ -28,6 +28,7 @@ defmodule Builder.MixProject do
       # {:dep_from_hexpm, "~> 0.3.0"},
       # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"},
       # {:sibling_app_in_umbrella, in_umbrella: true}
+      {:decimal, "~> 2.1"}
 ]
 end
 end
diff --git a/mix.lock b/mix.lock
index 5b94297..de4125c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -1,5 +1,6 @@
 %{
   "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
+  "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
   "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo:

decimal 패키지를 추가했다. mix.exs 파일과 mix.lock 파일에 관련 정보가 추가됐다.

이 상태에서 mix.exs 파일에 설정된 {:decimal, "~> 2.1"} 코드를 삭제한다. mix.lock 파일은 수정하지 않는다. 즉 mix.exs 파일에는 없어서 사용되지 않는데, mix.lock 파일에는 설정되어 있어서 다운로드 받는 종속성을 만든 상태다.

$ mix deps.unlock --check-unused
** (Mix) Unused dependencies in mix.lock file:

  * :decimal


$ echo $?
1

잘 검출된다.

참고


#TIL #windows 수신 대기 port 확인 및 해당 프로세스 죽이기

서버를 실행하면 특정 포트(port)를 열다가 실패하는 경우가 있다. 이미 수신 대기하고 있는 포트라는 메시지가 보인다. windows에서는 netstat, findstr, taskkill 프로그램을 사용해서 특정 포트를 수신 대기하고 있는 프로세스의 ID를 찾아 해당 프로세스를 죽일 수 있다.

netstat -nao | findstr 8081

8081 포트를 수신 대기하고 있는 PID(프로세스 ID)를 찾는다.

taskkill /f /pid [찾은 PID]

해당 프로세스를 죽인다.

링크


#TIL #elixirlang 긴 코드를 편하게 테스트하기

elixir 대화형 셸(interactive shell)인 iex를 사용하면 코드를 evaluation해서 모듈을 실시간으로 추가할 수 있고 실행할 수 있다. iex 프로그램을 실행한다. 함수를 실행한다. 코드를 수정한다. 다시 컴파일한다. 함수를 실행한다. 이런 빠른 이터레이션이 iex 안에서 가능하다.

간단한 코드면 괜찮은데, 긴 코드면 매번 iex 셸에서 입력하는 것도 귀찮아진다. iex 입력 히스토리를 기억해서 이전 히스토리를 뒤져 다시 실행하면 되지만 히스토리는 줄 단위로 기록된다. 여러 줄에 걸쳐 입력한 코드는 여러 번 히스토리를 뒤져야 한다.

elixir 스크립트 파일(.exs)을 만들어서 min run 으로 실행하면 된다.

query = from t in "tracks",
  join: a in "albums", on: t.album_id == a.id,
  where: t.duration > 900,
  select: [t.id, t.title, a.title]

이런 코드를 priv/playground.exs 파일로 저장한다.

$ mix run priv/playground.exs

mix run 으로 실행한다. mix run은 애플리케이션 종속성과 애플리케이션을 시작한 상태에서 추가로 특정 코드를 실행해 준다. 긴 코드도 편하게 테스트해 볼 수 있다. Programming Ecto(2019) 책에서 배웠다.


#TIL macOS에서 비밀번호를 아는 PDF 파일 암호화 풀기

연말 정산을 하다가 암호가 걸린 PDF를 첨부해야 할 일이 생겼다. 암호를 풀고 첨부해야 하는데, macOS에서 제공하는 미리보기 프로그램으로 PDF를 열어서 암호화하지 않은 PDF로 익스포트 하는데 잘되지 않는다. 암호화가 풀린 게 아니라 내가 입력한 비밀번호를 기억해서 바로 열어주는 것 같았다.

다행히 관련 프로그램을 homebrew 패키지 매니저로 설치해 실행해서 푸는 방법을 찾았다. qpdf 프로그램을 사용하면 된다.

$ brew install qpdf
$ qpdf --decrypt --password=xxxxx encrypted-filename.pdf decrypted-filename.pdf

암호화가 풀린 PDF 파일을 손쉽게 만들었다. 연말 정산 끝~

참고


#TIL macOS 파인더에서 숨김 파일 보기

파인더에서 숨김 파일이 보이지 않아 불편해 변경했다.

$ defaults write com.apple.finder AppleShowAllFiles -bool true;killall Finder

macOS에서는 프로그램 설정을 속성 목록(plist)을 편집해서 바꿀 수 있다. plist 파일을 찾아서 열어 해당 속성을 바꾸거나 셸에서 defaults 프로그램을 사용해 속성을 변경할 수 있다.

참고


#TIL winget 패키지 설치 옵션 덮어쓰기

winget을 사용하면 windows에서 커맨드라인으로 프로그램을 설치할 수 있다. apt-get, brew 처럼 손쉬운 설치가 windows에서 가능하다. 회사에서 코드 사이닝을 안 한 powershell 스크립트 파일을 사용 못 하게 도메인 정책으로 막아놓곤 해서 사용 못하던 chocolatey 대신 사용할 수 있다.

C:\> winget install -e --id JetBrains.dotUltimate

dotUltimate를 설치하면 너무 조용히 설치가 끝난다. 이렇게 조용할 리가? 설치 프로그램을 찾아보니 하나도 설치가 안 됐다.

C:\> winget install -e --id JetBrains.dotUltimate --override "/SpecificProductNames=ReSharper;dotTrace;dotMemory;dotCover;dotPeek;Rider /Silent=True /VsVersion=17.0"

JetBrains.dotUltimate.installer.yaml 파일을 참고해 --override 옵션을 사용해 변경했다. 디폴트로 설정한 VsVersion 이 안 맞아서 설치가 제대로 안 됐던 것 같다. 17.0 으로 변경하고 설치할 프로그램을 지정하니 잘 된다.


#TIL 윈도우 터미널(windows terminal)에서 git bash 사용하기

마이크로소프트에서 만든 윈도우 터미널(windows terminal)은 셸(shell)을 호스팅하는 애플리케이션(application)이다. 즉 이 애플리케이션을 사용해 cmd, bash, powershell 등을 띄울 수 있다. 화면 분할도 지원해서 그동안 사용하던 ConEmu를 버리고 윈도우 터미널을 사용하고 있다.

C:\> winget install --id=Microsoft.WindowsTerminal -e

윈도우 패키지 매니저인 winget을 사용해 간단히 설치할 수 있다.

Win+R wt 로 실행할 수 있다. 실행 파일 이름이 wt.exe 인 걸 몰라서 한참 찾았다.

이제 git bash를 세팅할 차례다. C-, 를 눌러 설정창을 연 후 ‘+ 새 프로필 추가’ 를 클릭한다.

  • 명령줄: “C:\git-sdk-64\git-cmd.exe” –no-cd –command=usr/bin/bash.exe -l -i
  • 시작 디렉터리: %USERPROFILE%

이제 윈도우 터미널에서도 git bash를 사용할 수 있다.


#TIL #elixirlang guard 절을 사용해 case 문에서 여러 조건을 검사

case val do
  200 -> true
  404 -> true
  _ -> false
end

case 문에서 200 이거나 404일 때, 검사를 한 번에 하려면 어떻게 하면 될까?

case val do
  n when n in [200, 400] -> true
  _ -> false
end
case val do
  n when n == 200 or n == 400 -> true
  _ -> false
end

when 키워드로 가드(guard)를 사용하면 된다. 다만 이걸 쓰기 위해서는 심볼을 바인딩하고 이 심볼을 when 으로 검사해야 한다. 위 코드에서 바인딩한 심볼이 n 이다. or 연산자를 써도 되고 in 연산자를 써도 된다.

참고


#TIL 환경 변수를 유지한 채 sudo가 필요할 때

사내 linux 가상 머신 인스턴스에 gitlab runner를 설치하려고 했을 때였던 걸로 기억한다.

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \
| sudo bash

gitlab 저장소를 추가한다. 이렇게 해야 이후에 업데이트도 쉽게 따라갈 수 있다. sudo apt-get update 명령만 내리면 최신 버전 정보를 가져오기 때문이다.

하지만 저 명령이 이상하게 동작하지 않는다. 특정 단계에서 시간이 오래 걸리며 시간 초과 에러가 난다. 스크립트 파일을 직접 다운로드 받아서 어디서 문제가 나는지 하나씩 실행해봤다. 아무런 문제 없이 잘 실행된다. 과연 뭐가 문제일까?

환경 변수 문제였다. 프록시 서버를 통해 인터넷 접근이 가능했는데, 그 설정을 https_proxy, http_proxy 환경 변수로 하고 있었다. sudo bash 명령은 환경 변수 유지 없이 superuser로 bash를 실행한다. 그래서 프록시 서버 세팅이 안 되니 인터넷에 접근해 파일을 받아오는 스크립트에서 시간 초과 에러가 발생한다.

curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" \
| sudo -E bash

-E 옵션을 사용하면 환경 변수를 유지한다. sudo bashsudo -E bash 로 바꿔주면 잘 동작한다.

링크


#TIL macOS에서 키 반복 입력 활성화하기

defaults write -g ApplePressAndHoldEnabled -bool false

Android Studio IDE의 vim 플러그인 ideavim을 설치해서 사용하다 보니 키 반복 입력을 활성화하는 방법을 알려주더라. 친절하다. 터미널을 열어서 위 명령어를 입력하면 된다.

링크


#TIL 에어팟(airpod) 자동 연결 옵션 변경

에어팟(AirPods, 2019) 2세대‘를 잘 사용하고 있다. iOS, iPadOS 14 이후로 지원되는 자동 전환을 켜서 사용하고 있었다. macOS도 지원돼서 기기간 연결이 잘 넘어간다. macOS에서 음악을 듣다가 별도 작업 없이 iOS로 듀오링고를 켜서 영어 공부를 하면 에어팟이 iOS에 연결이 되는 식으로 동작한다.

하지만 대부분의 부모가 그렇듯이 내 것인 줄 알았던 아이패드(iPad)가 자녀 교육용으로 넘어갈 때, 문제가 생긴다. 한창 코로나19 때문에 원격 수업을 많이 할 때, 문제가 발생했다. 화장실에 가려고 딸기부엉이 방 근처로 가면 원격 수업을 하는 아이패드에 내 에어팟이 연결되곤 했다. 방에선 갑자기 아이패드에서 소리가 안 들린다며 나를 찾기 시작한다.

/ddiary/assets/2022-06-25-til-how-to-stop-airpods-from-auto-switching-00.jpg

에어팟이 연결된 상태에서 블루투스(bluetooth) 설정으로 들어간다. 연결된 에어팟 옆에 보이는 i 버튼을 누르면 상세 설정에 들어갈 수 있다. 거기서 연결 메뉴를 볼 수 있는데, 자동 연결이 아닌 마지막으로 연결된 기기일 때만 연결되는 옵션을 선택한다. 이제는 소유권이 애매해진 아이패드에서는 자동연결을 끄고 사용 하고 있다.


#TIL macOS에서 steam 자동 실행 끄기

/ddiary/assets/2022-05-01-til-disable-auto-start-on-mac-00.jpg

시스템 환경설정 > 사용자 및 그룹 > 로그인 항목

목록에 보이면 클릭한 후 밑에 - 버튼을 눌러서 제거하면 된다.

macOS steam 프로그램에 환경 설정 > 인터페이스 > 컴퓨터를 시작할 때 Steam 자동 실행 옵션이 있지만 동작하지 않는다. Disable Steam auto start on Mac 글을 참고해서 자동 실행을 껐다.


#TIL #macOS 업그레이드 하기 전 USB로 부팅할 수 있는 안전 장치는 마련하자

/ddiary/assets/2022-04-10-til-macos-startup-security-utility-00.jpg

macOS 업데이트가 실패했다. 복구 모드로 와이파이를 잡고 운영체제를 다운로드받아 설치하는 것도 실패했다. 그냥 로컬에 저장된 복구 OS를 설치할걸. 그걸로 다시 시도해본다. 또 실패했다. USB에 macOS를 설치해 이걸로 부팅해서 설치해야겠다. 보안 칩이라니 있으니 좋겠지 하며 흘려들었던 T2 보안 칩이 부팅을 막는다.

한번 고생을 한 후에는 macOS 업데이트를 할 때, 항상 USB로 부팅할 수 있게 만들어 놓는다.

  1. Mac을 켠 다음 Apple 로고가 표시되면 곧바로 command(⌘)-R 키를 길게 누릅니다. Mac이 macOS 복구로 시동됩니다.
  2. 암호를 알고 있는 사용자를 선택하라는 메시지가 표시되면 사용자를 선택하고 ‘다음’을 클릭한 후 관리자 암호를 입력합니다.
  3. macOS 유틸리티 윈도우가 나타나면 메뉴 막대에서 유틸리티 > 시동 보안 유틸리티를 선택합니다.
  4. 인증하라는 메시지가 나타나면 ‘macOS 암호 입력’을 클릭한 다음 관리자 계정을 선택하고 해당 암호를 입력합니다.

- Apple T2 보안 칩이 탑재된 Mac의 시동 보안 유틸리티에 관하여

외부 또는 제거 가능한 미디어에서 시동 허용 라디오 버튼을 체크하면 된다.


#TIL #elixirlang 이 편한 match?/2

iex> match?(%{a: _}, %{a: 1, b: 2})
true
iex> match?(%{c: _}, %{a: 1, b: 2})
false

match?/2 매크로는 패턴에 매칭(matching)하는지를 boolean으로 리턴한다.

iex> match?(%{a: c}, %{a: 1, b: 2})
warning: variable "c" is unused (if the variable is not meant to be used, prefix it with an underscore)
  iex:17
true
iex> c
c
** (CompileError) iex:18: undefined function c/0

매크로 안에서 심볼에 값을 바인딩 되지 않는다. context를 공유하지 않는 macro hygiene이 적용된다.

iex> match?(%{a: x} when x > 2, %{a: 4, c: 2})
true
iex> match?(%{a: x} when x < 2, %{a: 4, c: 2})
false

안에서 쓸 수 없을 것 같은 가드 절(guard clause)도 사용할 수 있다.

그래서? 단독으로 사용할 때는 시큰둥해진다. 여기에 다른 함수를 끼얹는다면?

iex> list = [a: 1, b: 2, a: 3]
[a: 1, b: 2, a: 3]
iex> Enum.filter(list, &match?({:a, _}, &1))
[a: 1, a: 3]
iex> Enum.any?(list, &match?({_, 3}, &1))
true
iex> Enum.all?(list, &match?({_, 3}, &1))
false
iex> Enum.find(list, &match?({:a, x} when x > 2, &1))
{:a, 3}

Enum.filter/2, Enum.any?/2, Enum.all?/2, Enum.find/3술어(predicate)를 인자로 받는 모든 함수에 편하게 쓸 수 있다.


#TIL 닌텐도 스위치에 저장된 스크린샷 옮기기

닌텐도 스위치와 컴퓨터를 USB 케이블로 연결한다. 바로 인식하지 않는데, 별도 프로그램이 필요하다. 닌텐도 스위치 OS가 안드로이드인가보다. Android File Transfer 프로그램을 설치해야 한다.

닌텐도 스위치에서 설정 > 데이터 관리 > 화면 사진과 동영상 관리 > USB를 연결하여 컴퓨터에 복사 메뉴를 선택한다. 그 후 설치한 Android File Transfer 프로그램을 실행하면 닌텐도 스위치 디렉터리가 보인다.

macOS에서 테스트했다. windows에서도 잘 동작하지 싶다.


#TIL wifi 접속 QR코드 만들기

/ddiary/assets/2022-02-13-til-wifi-qr-code-00.jpg

apple 단축어 앱을 사용하면 wifi 접속 QR 코드를 만들 수 있다.

  1. 텍스트: WIFI:S:와이파이이름;T:WPA;P:패스워드;;
  2. QR 코드 생성
  3. 훑어보기

QR 코드를 찍으면 wifi 접속할 것인지 묻고 접속한다. 아이폰, 갤럭시 상관없이 동작한다. 둘 다 동작한다니 표준 스팩 같은 게 있는 게 아닐까? 표준 문서는 못 찾았다. 대신 email, vCard, sms, facetime, map, calendar event, wifi network 설정이 적힌 barcode 스캐닝 라이브러리 ZXing의 Barcode Contents 문서를 찾았다.

만드는 법을 설명한 youtube를 보고 따라서 액자를 만들어봤다. 액자가 구려서 안 예쁘다. airbnb 숙소를 운영하는 것도 아니어서 사진만 찍고 폐기했다. 친척이 놀러 왔을 때, wifi 비번을 물어보면 apple 단축어 앱으로 만든 QR 코드를 보여주는 거로 충분할 것 같다. 그래도 찍기만 했던 QR 코드를 만드니 재미있었다.

PS: 사진에 찍힌 QR 코드는 와이파이 SSID와 패스워드는 임의로 만든 거니 스캔해도 쓸모가 없다.

참고