파일 불러오기
파일 불러오기 (Requiring files)
프로그램이 커지면 한 파일에 모든 걸 담는 건 유지보수가 어려워져요. Crystal에서는 require로 다른 파일을 불러와 여러 파일로 프로그램을 나눌 수 있어요.
출처: Crystal 공식 문서
본문
작은 조각이나 벤치마크 코드는 한 파일로 충분해요. 하지만 큰 프로그램은 여러 파일로 나눠야 유지보수하고 이해하기 좋아요.
컴파일러가 다른 파일을 처리하게 하려면 require "..."를 써요. 단일 인자(문자열 리터럴)를 받고, 여러 형태가 있어요.
파일이 한 번 require되면 컴파일러가 그 절대 경로를 기억하고, 나중에 같은 파일을 require하는 것은 무시돼요.
require "filename"
이것은 require 경로에서 "filename"을 찾아요.
기본적으로 require 경로는 두 곳을 포함해요.
- 현재 작업 디렉토리 기준
lib디렉토리(여기서 의존성을 찾아요) - 컴파일러에 포함된 표준 라이브러리의 위치
이 두 곳만 검색돼요.
컴파일러가 사용하는 정확한 경로는 crystal env CRYSTAL_PATH로 조회할 수 있어요.
$ crystal env CRYSTAL_PATH
lib:/usr/bin/../share/crystal/src
이 검색 경로는 CRYSTAL_PATH 환경 변수를 정의해서 덮어쓸 수 있어요.
검색은 이렇게 진행돼요.
- require 경로에서 "filename.cr"라는 파일이 발견되면 그것이 require돼요.
- "filename"이라는 디렉토리가 발견되고, 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
- "filename"이라는 디렉토리에 "src" 디렉토리가 있고 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
- 그렇지 않으면 컴파일 타임 오류가 발생해요.
두 번째 규칙은 이런 구성 외에도:
- project
- src
- file
- sub1.cr
- sub2.cr
- file.cr (requires "./file/*")
이렇게도 쓸 수 있다는 뜻이에요.
- project
- src
- file
- file.cr (requires "./*")
- sub1.cr
- sub2.cr
취향에 따라 조금 더 깔끔할 수 있죠.
세 번째 규칙은 프로젝트의 전형적인 디렉토리 구조 덕분에 매우 편리해요.
- project
- lib
- foo
- src
- foo.cr
- bar
- src
- bar.cr
- src
- project.cr
- spec
- project_spec.cr
즉, "lib/{project}" 안에 각 프로젝트의 디렉토리(src, spec, README.md 등)가 존재해요.
예를 들어 project.cr에 require "foo"를 넣고 프로젝트 루트에서 crystal src/project.cr를 실행하면, lib/foo/foo.cr에서 foo를 찾아요.
네 번째 규칙은 세 번째 규칙에 두 번째 규칙을 적용한 거예요.
컴파일러를 다른 곳, 예를 들어 src 폴더에서 실행하면 lib가 경로에 없어서 require "foo"를 해석할 수 없어요.
require "./filename"
이것은 require 표현식을 담은 파일을 기준으로 "filename"을 찾아요.
검색은 이렇게 진행돼요.
- 현재 파일 기준으로 "filename.cr" 파일이 발견되면 require돼요.
- "filename" 디렉토리가 발견되고 그 바로 아래에 "filename.cr" 파일이 있으면 require돼요.
- 그렇지 않으면 컴파일 타임 오류가 발생해요.
이 상대 경로는 주로 프로젝트 안에서 다른 파일을 참조할 때 써요. spec에서 코드를 참조할 때도 사용돼요.
require "../src/project"
다른 형태
두 경우 모두 중첩 이름을 쓸 수 있고 중첩 디렉토리에서 검색돼요.
require "foo/bar/baz"는 require 경로에서 "foo/bar/baz.cr", "foo/bar/baz/baz.cr", "foo/src/bar/baz.cr" 또는 "foo/src/bar/baz/baz.cr"을 찾아요.require "./foo/bar/baz"는 현재 파일 기준으로 "foo/bar/baz.cr" 또는 "foo/bar/baz/baz.cr"을 찾아요.
현재 파일 기준으로 상위 디렉토리에 접근하려면 "../"를 쓸 수도 있어서, require "../../foo/bar"도 동작해요.
이 모든 경우에 특별한 *와 ** 접미사를 쓸 수 있어요.
require "foo/*"는 "foo" 디렉토리 아래의 모든 ".cr" 파일을 require하지만, "foo" 안의 하위 디렉토리는 포함하지 않아요.require "foo/**"는 "foo" 디렉토리 아래와 "foo" 안의 하위 디렉토리 아래의 모든 ".cr" 파일을 재귀적으로 require해요.
더 알아보기
- 파일을 한 번 require하면 컴파일러가 절대 경로를 기억하므로, 같은 파일을 여러 번 require해도 한 번만 처리돼요.
lib는 의존성, 표준 라이브러리는 컴파일러가 기본 검색 경로로 사용한다는 점을 기억하면 돼요.