스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift Regex: 기초를 넘어
Swift Regex로 문자열 처리의 기초를 뛰어넘으세요. Regex의 개요 및 작동 원리를 소개하고, Foundation의 풍부한 데이터 파서를 살펴보며, 내 파서를 통합하는 방법을 알아보고, 캡처에 대한 자세한 내용을 알아봅니다. 또한 손쉽게 문자열을 일치시키고 Regex 기반 알고리즘을 활용하기 위한 모범 사례를 제공합니다.
리소스
- SE-0350: Regex type and overview
- SE-0351: Regex builder DSL
- SE-0354: Regex literals
- SE-0355: Regex syntax
- SE-0357: Regex-powered algorithms
관련 비디오
WWDC22
WWDC21
-
다운로드
♪ ♪
안녕하세요 저는 Richard입니다 전 Swift Standard Library 팀의 엔지니어인데요 Swift 정규식에 대해 많은 걸 이야기해 봅시다 Swift 5.7의 문자열 프로세싱 기능이 더욱 강력해지고 있는데요 Regex 타입부터 시작해 보죠 Swift Standard Library의 신규 타입인데요 언어 빌드 Regex 리터럴 구문은 Regex를 훨씬 더 고급으로 만들어 줍니다 마지막으로 결과 빌더 API는 RegexBuilder라고 하는데요 이것은 도메인 전용 언어인 DSL로서 Regex 구문의 단순함과 결과 빌더의 결합성을 이용하며 Regex의 가독성을 다른 차원으로 끌어 올립니다 Swift Rgex가 문자열 처리를 쉽게 할 수 있는 배경은 제 동료 Michael이 진행하는 아래 세션에서 확인해 보시죠 Swift Regex의 아주 단순한 예시를 살펴 볼게요 이런 데이터 문자열이 있다고 가정해 봅시다 이 문자열에서 유저 ID를 매치하고 추출해 보겠습니다 NSRegularExpression를 사용하는 것처럼 텍스트로 정규 표현식을 만들 수 있습니다 이건 "user_id:"과 매치되고 그 다음 공백이 없거나 있거나 많고 숫자가 하나 또는 많이 있습니다 여기서 다른 점은 우리가 Regex 타입의 값을 만들고 있다는 점인데요 이건 Swift Standard Library의 새로운 타입입니다 그 다음 문자열의 'firstMatch' 알고리즘으로 이 Regex가 정의한 패턴의 첫 번째 발생을 찾아 이런 식으로 전체 매치를 출력해 보겠습니다 제 Regex 문자열은 컴파일 타임에 알려졌기 때문에 컴파일러가 구문 오류를 확인하고 Xcode가 구문 강조할 수 있게 Regex 리터럴로 바꾸겠습니다 하지만 가독성과 커스터마이제이션을 위해 Regex 빌더 DSL을 사용할 수 있는데요 Regex 빌더가 있으면 Regex 콘텐츠 읽기가 본래의 Swift API를 읽는 것 만큼이나 쉽습니다 이번 세션에서는 Regex의 작동 방식과 작업 흐름에 적용할 수 있는 방법을 보여드리겠습니다 Regex는 Regex 엔진에 의해 실행되는 프로그램입니다 Regex를 실행할 때 엔진은 입력 문자열을 가져와 문자열의 처음부터 끝까지 매칭을 수행합니다 아주 간단한 예시를 살펴 보죠 하나 또는 많은 글자 "a"로 시작되는 문자열과 매치되고 숫자가 하나 또는 더 많이 이어지는 Regex입니다 입력값 "aaa12"를 매칭하기 위해 매칭 알고리즘 중 'wholeMatch'를 써보겠습니다 Regex 엔진은 입력값의 첫 문자부터 시작하는데요 먼저, 이건 하나 또는 더 많은 문자 "a"와 매치되는데요 이 지점에서 문자 "1"에 도달하게 됩니다 이 문자를 문자 "a"에 대해 매칭해 보려고 하는데요 하지만 매칭되지 않죠 그래서 Regex 엔진은 하나 또는 더 많은 숫자를 매칭하기 위해 다음 패턴으로 넘어갑니다 문자열 끝에 도달하여 매칭에 성공했네요 남은 시간 동안 이 실행 모델에 대해 좀더 설명을 드려볼게요 Regex 엔진을 기반으로 Regex가 빌드되어 있고 Regex 빌더 DSL과 Regex 사용 알고리즘은 Regex의 힘과 표현을 확장하죠
Regex 사용 알고리즘은 가장 흔한 연산을 제공하는 컬렉션 기반 API로 firstMatch 알고리즘은 문자열에서 Regex의 첫 발생을 찾고 wholeMatch는 전체 문자열을 Regex에 매칭하며 prefixMatch는 문자열의 프리픽스를 Regex에 매칭합니다 Swift Standard Library는 매칭 외에도 Regex 기반의 서술, 대체, 공백 제거 코드 분할을 위한 API도 추가했습니다 Regex는 제어 흐름 문장 내 Swift의 패턴 매칭 구문에서도 사용될 수 있어 문자열에서 변경하는 것이 그 어느 때보다 쉬워졌습니다 마지막으로 Foundation에서는 Regex 빌더와 알고리즘 최상단에 빌더와의 자연스러운 작업을 위해 지원을 시작했습니다 지원 내용은 다름 아닌 여러분이 이미 사용하고 있는 형식자와 파서인데요 날짜와 숫자에 대한 형식자와 파서 등이 있습니다 이 API에 대해 더 알아보고 싶으시다면 WWDC21의 다음 세션을 참고해 주세요 올해 Foundation에는 URL 포매팅과 파싱이 추가될 겁니다 Foundation 내 Regex 지원으로 Regex 빌더에 Foundation 파서를 직접 포함시킬 수 있습니다 이와 같은 입출금 내역서를 파싱하기 위해서는 커스텀 포맷에 Foundation 제공 날짜 파서를 도메인 특정 파싱 전략과 통화 파서를 쓸 수 있습니다 이건 정말 대단한 일인데 코너 케이스를 처리하는 기존의 짱짱한 파서들에서 Regex를 만들 수 있고 로컬라이제이션을 지원할 수 있으며 Regex 빌더 DSL의 표현과 함께 쓸 수 있기 때문이죠 Regex를 작업 흐름에 적용하는 방법을 알아보기 위해 예제를 함께 풀어 봅시다 XCTest 기반 유닛 테스트의 로그를 파싱하고자 제가 쓰던 스크립트입니다 테스트 로그의 시작과 끝은 모두 테스트 스위트의 상태죠 XCTest는 모든 테스트 케이스를 실행하고 그 상태를 보고합니다 오늘은 로그의 맨 첫 줄과 마지막 줄을 파싱해 볼게요 테스트 스위트에 대한 정보인데요 먼저, RegexBuilder를 불러올 겁니다 RegexBuilder는 RegexBuilder DSL을 제공하는 Swift Standard Library 내 새로운 모듈인데요 Regex는 Regex의 바디를 나타내는 후행 클로저로 초기화될 수 있습니다 로그 메시지 예시를 볼까요? 이 로그에서 신경 써야 할 변수 부분 문자열이 3개입니다 테스트 스위트의 이름 테스트 스위트의 상태 시작, 통과, 실패 여부를 나타내고요 마지막 타임스탬프입니다 저는 이 3개의 변수 문자열 패턴을 찾으며 말 그대로 이 줄의 다른 부분을 파싱할 수 있는데요 로그 메시지는 'test suite'라는 단어로 시작해 그 다음에는 공백과 작은 따옴표가 옵니다 그 다음 테스트 스위트의 이름을 파싱해 보겠습니다 이름은 식별자로서 식별자에는 대문자, 소문자 숫자가 포함될 수 있습니다 하지만 첫 번째 문자는 절대 숫자가 되선 안 됩니다
처음의 글자를 매칭하기 위해 커스텀 캐릭터 클래스를 만들고 글자나 0부터 9까지의 숫자일 수 있는 없거나 있는 문자에 매칭합니다 정말 깔끔하고 읽기에도 좋습니다 하지만 다소 번거롭습니다 많은 분들께서 문자로 된 Regex 구문에 익숙하실텐데 RegexBuilder에서는 간결한 Regex 리터럴을 바디에 직접 포함시킬 수 있습니다 Regex 리터럴의 시작과 끝은 /입니다 Swift는 이를 위해 올바른 강타입을 추론하죠 여기선 "Hello, WWDC!"라는 부분 문자열을 매칭하죠 따라서 이 출력 타입은 부분 문자열입니다 하지만 고급 Regex 리터럴의 진짜 멋진 점은 강타입 캡처링 그룹이라는 점입니다 2개 숫자를 연도로 캡처하려고 이 그룹을 썼는데요 이 캡처링 그룹에 'year'라는 이름을 부여하겠습니다 그럼 다른 출력 타입에 다른 부분 문자열이 나오죠 이따가 스트링 정보를 추출하기 위해 캡쳐를 사용할 수 있는 방법을 보여드리겠습니다 Swift는 표준 Regex 리터럴 외에도 확장 Regex 리터럴 또한 지원하는데요 파운드 슬래시(#/)로 시작하고 슬래시 파운드(/#)로 끝납니다 확장 리터럴에서는 무의미한 공백도 허용됩니다 이 모드에서는 패턴을 여러 줄로 자를 수 있죠 제 RegexBuilder에는 Regex 리터럴이 포함됐는데 깔끔하면서도 친숙하죠? 테스트 이름을 파싱한 후 작은 따옴표와 공백을 파싱합니다 이제 테스트 상태네요 시작, 실패, 통과 등 다양한 유형이 있는데요 하나의 옵션을 매칭하기 위해 ChoiceOf를 사용합니다 ChoiceOf는 다양한 서브 패턴을 매칭합니다 우리에게 딱 필요한 것이죠 이제 상태 바로 다음에 오는 걸 파싱할 건데요 공백과 공백 사이에 'at'이라는 문자가 오네요 나머지 문자열은 타임스탬프이고요 이건 어떤 문자든 하나 또는 많은 문자로 매칭할 수 있어요 하지만 예시를 더 살펴 보면 로그 메시지가 간혹 마침표(.)로 끝나기도 하죠 마침표가 존재할 경우에는 '선택적으로' 사용합니다
입력값을 Regex에 매칭하기 위해서 제공 매칭 알고리즘 중 하나를 사용하세요 문자열 전체를 Regex에 매칭하는 wholeMatch를 사용합시다 이 알고리즘으로 각 로그 메시지를 캐칭하고 매치된 콘텐츠를 출력할 건데요 매칭이 됐네요! 하지만 궁금한 건 이게 끝이 아니죠 우리가 신경써야 할 정보를 추출하기도 해야 하니까요 테스트 이름 테스트 상태, 타임스탬프 이런 정보들이요 그럼 Regex의 가장 멋진 특색을 사용해 봅시다 바로 '캡처'입니다! Capture는 매칭 중 입력값의 일부를 저장합니다 RegexBuilder에서 'Capture'로 이용 가능하며 Regex 구문에서 이렇게 괄호쌍으로 표현됩니다 캡처링은 매칭된 부분 문자열을 출력값 튜플 타입에 매칭하는데 출력물 튜플 타입은 Regex 전체를 매칭한 전체 부분 문자열로 시작해 첫 번째 캡처와 두 번째 캡처 등이 그 뒤에 이어집니다 이 매칭 알고리즘은 출력값 튜플을 획득할 수 있는 Regex Match로 반환됩니다 전체 매치 첫 번째 캡처 두 번째 캡처입니다
테스트 스위트 로그 Regex에서 캡처를 사용해 보겠습니다 테스트 스위트의 이름과 상태, 타임스탬프를 캡처했는데요 일부 입력값에 대해 다시 실행시키고 캡처한 3가지를 출력해 봅시다 성공적인 매치로 보이네요! 이름, 상태, 타임스탬프를 출력했습니다
하지만 잘 보면 날짜에서 뭔가가 좀 이상하죠 입력물의 마침표가 캡처의 일부로 포함됐거든요 다시 돌아가서 오류를 찾아볼까요? 타임스탬프 Regex에 집중해 잘못된 걸 찾아 보겠습니다 '어떤 문자든 하나 또는 많은 문자'라는 패턴이 타임스탬프의 처음 숫자부터 라인 맨 마지막까지 쭉 모든 걸 써버렸거든요 따라서 그 아래에 있는 패턴은 매칭이 불가능합니다
이 OneOrMore를 reluctant로 만들면 되는데요 Reluctant는 반복 행동 케이스입니다 이 4가지는 Swift Regex에서 '반복'이라고 부르는 것들입니다 반복은 기본적으로 eager입니다 가능한 한 많은 발생을 매칭하죠 이전의 예시를 사용해 보겠습니다 여기서 아무 문자가 하나 또는 많은 경우에 열심히 매칭하면 첫 번째 문자로 시작해서 입력값의 끝까지 빠짐없이 어떤 문자든 받아들입니다 그럼 Regex 엔진은 마침표를 선택적으로 매칭하고자 옮겨가죠 매칭되는 마침표는 없지만 선택적이니까 성공한 거죠 wholeMatch 알고리즘을 실행 중이고 입력값과 Regex 패턴 모두 끝까지 도달하여 매칭이 성공하였습니다 매칭에는 성공했지만 반점은 OneOrMore의 일부로 예상치 못하게 캡처되었는데요
반복 행동을 reluctant로 바꾸면 Regex 엔진은 반복을 약간 다르게 매칭합니다 가능한 한 적은 문자를 매칭하죠 Regex 엔진이 입력 문자역을 매칭하면 이번에는 먼저 Regex 나머지를 모두 매칭하면서 앞으로 나갑니다 반복 발생을 쓰기 전까지 말이죠 나머지가 매칭되지 않으면 엔진은 반복 행동으로 백트래킹하여 추가 발생을 사용합니다 마지막 문자와 마침표까지 빠르게 나가 봅시다 즉각 행동과는 다르게 Regex 엔진은 초기에 마침표를 OneOrMore의 일부로 쓰지 않습니다 대신 'Optionally(".")' 패턴에 매칭하려 하죠 이 매치들과 Regex 엔진은 패턴 끝까지 도달합니다 따라서 매칭이 성공하죠 그리고 후행 마침표 없이 올바른 캡처를 생성합니다
eager는 반복을 사용하는 Regex를 만들 때 기본 행동이기 때문에 이것이 의도한 매치에 미치는 영향을 생각해야 합니다 추가 인수를 통과함으로써 각 반복 수준에서 행동을 지정할 수 있습니다 아니면 repetitionBehavior 제어자를 사용해 행동 지정을 하지 않은 모든 반복에 대해 중단해야죠 타임스탬프 반복 행동을 reluctant로 제어했기에 이제 매칭에서 마침표 없이 올바른 타임스탬프를 추출합니다
Regex로 돌아갑시다 테스트 상태를 추출하기 위해 Capture를 사용할 때 이 타입은 'Substring'이 됩니다 하지만 부분 문자열을 커스텀 데이터 구조와 같이 프로그래밍 친화적으로 바꾸면 훨씬 더 좋을 겁니다 여기서 트랜스포밍 캡처를 사용할 수 있습니다 트랜스포밍 캡처는 트랜스폼 클로저가 있는 'Capture'죠 Regex 엔진은 매칭되자마자 매칭된 부분 문자열에서 트랜스폼 클로저를 호출합니다 원하는 타입의 결과를 생성하죠 일치 Regex 출력 타입은 클로저의 반환 타입이 됩니다 여기에서는 문자열에서 Int의 생성자로 캡처를 변환하여 출력 튜플 타입 내에 선택적인 Int를 가져옵니다 비선택적 출력값 획득에는 TryCapture가 도움이 됩니다 TryCapture는 Capture의 변형으로 선택적인 출력값을 반환하고 출력 타입에서 선택성을 제거합니다 매칭 중 반환되는 게 없으면 Regex 엔진은 백트래킹하여 대체 경로를 시도합니다 TryCapture는 실패 가능한 생성자가 있고 캡처를 변환할 때 가장 유용합니다 캡처된 테스트 상태 저장에 딱 맞는 건 열거일 겁니다 하나 정의해 보죠 저는 TestStatus 열거형을 시작, 통과, 실패라는 3가지 케이스로 정의했습니다 원시 문자열 값은 이 열거형을 문자열에서 초기화하게 합니다
여기서 트랜스폼을 추가해 TryCapture로 변경하고 트랜스폼 클로저에서 TestStatus 생성자를 호출해 매치된 부분 문자열을 TestStatus 값으로 바꿉니다 이제 일치하는 출력 타입은 'TestStatus'입니다 이런 커스텀 데이터 구조를 사용하여 Regex 매치 출력 타입을 안전하게 할 수 있습니다 Regex로 돌아가보죠 개선하고 싶은 점이 하나 더 있는데요 와일드카드 패턴을 사용해 타임스탬프를 매칭했는데 그럼 부분 문자열을 생성하게 됩니다 앱이 타임스탬프를 이해하고 싶다면 부분 문자열을 다른 데이터 구조로 또 파싱해야 한단 거죠 세션 초반에 Foundation의 Swift Regex 지원을 말씀드렸었죠 Regex를 산업 최강 파서로 제공하면서요 따라서 날짜를 부분 문자열로 파싱하는 대신에 Foundation의 ISO 8601 날짜 파서로 변경하여 타임스탬프를 날짜로 파싱할 수 있습니다 추론 타입은 이 Regex가 'Date'를 출력한다고 보여주죠
입력값에 wholeMatch를 실행하면 날짜 문자열이 Date 값으로 파싱된 게 보입니다 문자열 프로세싱 태스크에서 Foundation 날짜 파서처럼 Regex의 짱짱한 파서에 접근하는 것은 정말 편리합니다 다음으로 고급 특색을 보여드리겠습니다 Regex 어디에선가 정의된 기존 파서를 재사용하는 건데요 테스트 케이스 지속 기간 파싱 예시로 확인해 봅시다 지속 기간은 0.001 같은 부동 소수점 숫자입니다 당연히 여기서도 가장 좋은 정답은 로컬라이제이션을 완전 지원하는 Foundation이 제공하는 부동 소수점 파서죠 하지만 오늘은 감춰진 것과 함께 Regex engine에게 기존 파서를 이용하라고 시켜 부동 소수점 숫자의 지속 기간 파싱법을 보여드리고자 합니다 'strtod'는 C 표준 라이브러리의 함수입니다 문자열 포인터를 취하고 기반 문자열을 파싱하며 매치의 끝 포지션을 끝 포인터에 할당합니다 그럼 C 방식으로 지속 기간을 파싱해 봅시다 파싱을 위해 스스로 파서 타입 정의가 가능합니다 그리고 CustomConsumingRegexComponent 프로토콜에 일치시키면 되죠
저는 'CDoubleParser'라는 이름의 구조를 정의했습니다 여기서 RegexOutput은 'Double'인데요 우리가 Double 숫자를 파싱하고 있기 때문이죠 consuming 메소드에서는 C 표준 라이브러리에서 더블 파서를 호출하면 문자열 포인터가 파서를 통과하고 숫자가 돌아옵니다 메소드 바디에서는 withCString 메소드를 사용해 시작 주소를 획득합니다 그 다음 strtod C 함수를 호출하여 시작 주소와 포인터를 보내 결과 끝 주소를 받습니다 그 다음 오류를 확인하죠 파싱이 성공하면 끝 주소가 시작 주소보다 더 큽니다 그렇지 않으면 파싱이 실패하여 아무 것도 반환되지 않죠 C API가 생성한 포인터에서 매치의 상계를 계산합니다 마지막으로 매치의 상계와 숫자 출력값을 반환합니다 Regex로 돌아가서 CDoubleParser를 사용해보죠 출력 타입은 'Double'이 될 것으로 추론됩니다 wholeMatch를 호출하여 파싱된 수를 인쇄하면 출력값은 예상한 대로 0.001이 됩니다 우리는 오늘 Swift Regex의 흔한 사용법과 고급 사용법 문자열 프로세싱의 힘을 앱에 통합시킬 수 있는 Swift 5.7의 새 특색에 대해 알아보았습니다 Swift Regex 사용 시 간결함과 가독성의 중심을 잘 맞추는 것이 중요합니다 특히 RegexBuilder DSL과 Regex 리터럴을 섞을 때요 날짜, URL 같은 흔한 패턴을 만났을 땐 항상 Foundation이 제공하는 산업 최강 파서를 쓰세요 커스텀 코드 패턴을 파싱할 땐 오류가 쉽게 날 수 있거든요
Swift Regex 정보를 원하신다면 Swift Evolution의 선언적 문자열 처리 제안 시리즈를 확인하세요 Swift를 통한 문자열 처리를 즐겨 보시길 바랍니다 감사합니다 즐거운 시간 보내세요
-
-
0:39 - Regex matching "Hi, WWDC22!"
Regex { "Hi, WWDC" Repeat(.digit, count: 2) "!" }
-
1:06 - Simple Regex from a string
let input = "name: John Appleseed, user_id: 100" let regex = try Regex(#"user_id:\s*(\d+)"#) if let match = input.firstMatch(of: regex) { print("Matched: \(match[0])") print("User ID: \(match[1])") }
-
1:56 - Simple Regex from a literal
let input = "name: John Appleseed, user_id: 100" let regex = /user_id:\s*(\d+)/ if let match = input.firstMatch(of: regex) { print("Matched: \(match.0)") print("User ID: \(match.1)") }
-
2:08 - Simple regex builder
import RegexBuilder let input = "name: John Appleseed, user_id: 100" let regex = Regex { "user_id:" OneOrMore(.whitespace) Capture(.localizedInteger) } if let match = input.firstMatch(of: regex) { print("Matched: \(match.0)") print("User ID: \(match.1)") }
-
2:38 - A trivial Regex interpreted by the Regex engine
let regex = Regex { OneOrMore("a") OneOrMore(.digit) } let match = "aaa12".wholeMatch(of: regex)
-
3:49 - Regex-powered algorithms
let input = "name: John Appleseed, user_id: 100" let regex = /user_id:\s*(\d+)/ input.firstMatch(of: regex) // Regex.Match<(Substring, Substring)> input.wholeMatch(of: regex) // nil input.prefixMatch(of: regex) // nil input.starts(with: regex) // false input.replacing(regex, with: "456") // "name: John Appleseed, 456" input.trimmingPrefix(regex) // "name: John Appleseed, user_id: 100" input.split(separator: /\s*,\s*/) // ["name: John Appleseed", "user_id: 100"] switch "abc" { case /\w+/: print("It's a word!") }
-
5:14 - Use Foundation parsers in regex builder
let statement = """ DSLIP 04/06/20 Paypal $3,020.85 CREDIT 04/03/20 Payroll $69.73 DEBIT 04/02/20 Rent ($38.25) DEBIT 03/31/20 Grocery ($27.44) DEBIT 03/24/20 IRS ($52,249.98) """ let regex = Regex { Capture(.date(format: "\(month: .twoDigits)/\(day: .twoDigits)/\(year: .twoDigits)")) OneOrMore(.whitespace) OneOrMore(.word) OneOrMore(.whitespace) Capture(.currency(code: "USD").sign(strategy: .accounting)) }
-
6:24 - XCTest log regex (version 1)
import RegexBuilder let regex = Regex { "Test Suite '" /[a-zA-Z][a-zA-Z0-9]*/ "' " ChoiceOf { "started" "passed" "failed" } " at " OneOrMore(.any) Optionally(".") }
-
6:25 - Test our Regex against some inputs
let testSuiteTestInputs = [ "Test Suite 'RegexDSLTests' started at 2022-06-06 09:41:00.001", "Test Suite 'RegexDSLTests' failed at 2022-06-06 09:41:00.001.", "Test Suite 'RegexDSLTests' passed at 2022-06-06 09:41:00.001." ] for line in testSuiteTestInputs { if let match = line.wholeMatch(of: regex) { print("Matched: \(match.output)") } }
-
10:28 - Example of capture
let regex = Regex { "a" Capture("b") "c" /d(e)f/ } if let match = "abcdef".wholeMatch(of: regex) { let (wholeMatch, b, e) = match.output }
-
11:10 - XCTest log regex (version 2, with captures)
import RegexBuilder let regex = Regex { "Test Suite '" Capture(/[a-zA-Z][a-zA-Z0-9]*/) "' " Capture { ChoiceOf { "started" "passed" "failed" } } " at " Capture(OneOrMore(.any)) Optionally(".") }
-
11:21 - Test our Regex (version 2) against some inputs
let testSuiteTestInputs = [ "Test Suite 'RegexDSLTests' started at 2022-06-06 09:41:00.001", "Test Suite 'RegexDSLTests' failed at 2022-06-06 09:41:00.001.", "Test Suite 'RegexDSLTests' passed at 2022-06-06 09:41:00.001." ] for line in testSuiteTestInputs { if let (whole, name, status, dateTime) = line.wholeMatch(of: regex)?.output { print("Matched: \"\(name)\", \"\(status)\", \"\(dateTime)\"") } }
-
11:51 - XCTest log regex (version 3, with reluctant repetition)
import RegexBuilder let regex = Regex { "Test Suite '" Capture(/[a-zA-Z][a-zA-Z0-9]*/) "' " Capture { ChoiceOf { "started" "passed" "failed" } } " at " Capture(OneOrMore(.any, .reluctant)) Optionally(".") }
-
15:20 - Example of transforming capture
Regex { Capture { OneOrMore(.digit) } transform: { Int($0) // Int.init?(_: some StringProtocol) } } // Regex<(Substring, Int?)>
-
15:55 - Example of transforming capture and removing optionality
Regex { TryCapture { OneOrMore(.digit) } transform: { Int($0) // Int.init?(_: some StringProtocol) } } // Regex<(Substring, Int)>
-
16:21 - XCTest log regex (version 4, with transforming capture)
enum TestStatus: String { case started = "started" case passed = "passed" case failed = "failed" } let regex = Regex { "Test Suite '" Capture(/[a-zA-Z][a-zA-Z0-9]*/) "' " TryCapture { ChoiceOf { "started" "passed" "failed" } } transform: { TestStatus(rawValue: String($0)) } " at " Capture(OneOrMore(.any, .reluctant)) Optionally(".") } // Regex<(Substring, Substring, TestStatus, Substring)>
-
17:23 - XCTest log regex (version 5, with Foundation ISO 8601 date parser)
let regex = Regex { "Test Suite '" Capture(/[a-zA-Z][a-zA-Z0-9]*/) "' " TryCapture { ChoiceOf { "started" "passed" "failed" } } transform: { TestStatus(rawValue: String($0)) } " at " Capture(.iso8601( timeZone: .current, includingFractionalSeconds: true, dateTimeSeparator: .space)) Optionally(".") } // Regex<(Substring, Substring, TestStatus, Date)>
-
18:19 - XCTest log duration parser
let input = "Test Case '-[RegexDSLTests testCharacterClass]' passed (0.001 seconds)." let regex = Regex { "Test Case " OneOrMore(.any, .reluctant) "(" Capture { .localizedDouble } " seconds)." } if let match = input.wholeMatch(of: regex) { print("Time: \(match.output)") }
-
19:16 - CDoubleParser definition
import Darwin struct CDoubleParser: CustomConsumingRegexComponent { typealias RegexOutput = Double func consuming( _ input: String, startingAt index: String.Index, in bounds: Range<String.Index> ) throws -> (upperBound: String.Index, output: Double)? { input[index...].withCString { startAddress in var endAddress: UnsafeMutablePointer<CChar>! let output = strtod(startAddress, &endAddress) guard endAddress > startAddress else { return nil } let parsedLength = startAddress.distance(to: endAddress) let upperBound = input.utf8.index(index, offsetBy: parsedLength) return (upperBound, output) } } }
-
20:13 - Use CDoubleParser in regex builder
let input = "Test Case '-[RegexDSLTests testCharacterClass]' passed (0.001 seconds)." let regex = Regex { "Test Case " OneOrMore(.any, .reluctant) "(" Capture { CDoubleParser() } " seconds)." } // Regex<(Substring, Double)> if let match = input.wholeMatch(of: regex) { print("Time: \(match.1)") }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.