12345678910module build1112import os131415const gradle_files = ['app/build.gradle.kts', 'app/build.gradle', 'build.gradle.kts', 'build.gradle']1617181920pub fn project_version(root string) !string {21 if value := file_version(os.join_path(root, 'VERSION')) {22 return checked(value, 'VERSION')23 }24 if value := line_version(os.join_path(root, 'build.zig.zon'), '.version') {25 return checked(value, 'build.zig.zon')26 }27 if value := line_version(os.join_path(root, 'v.mod'), 'version') {28 return checked(value, 'v.mod')29 }30 if value := line_version(os.join_path(root, 'package.json'), '"version"') {31 return checked(value, 'package.json')32 }33 if value := line_version(os.join_path(root, 'Cargo.toml'), 'version') {34 return checked(value, 'Cargo.toml')35 }36 if value := plist_version(os.join_path(root, 'Info.plist')) {37 return checked(value, 'Info.plist')38 }39 for file in gradle_files {40 if value := gradle_version(os.join_path(root, file)) {41 return checked(value, file)42 }43 }44 return error('no version in ${root} — add a VERSION file holding the version to publish')45}46474849fn checked(value string, source string) !string {50 if value.len == 0 || value.len > 32 || !value.bytes().all(is_version_byte(it)) {51 return error('${source} says the version is "${value}", which is not 1–32 letters, digits, dots and dashes')52 }53 return value54}5556fn is_version_byte(character u8) bool {57 return (character >= `a` && character <= `z`) || (character >= `A` && character <= `Z`)58 || (character >= `0` && character <= `9`) || character == `.` || character == `-`59}606162fn file_version(path string) ?string {63 text := os.read_file(path) or { return none }64 value := text.trim_space()65 if value == '' {66 return none67 }68 return value69}7071727374fn line_version(path string, key string) ?string {75 text := os.read_file(path) or { return none }76 for line in text.split_into_lines() {77 trimmed := line.trim_left(' \t')78 if trimmed == '' || trimmed.starts_with('//') || trimmed.starts_with('#') {79 continue80 }81 if !trimmed.starts_with(key) {82 continue83 }84 if value := quoted_value(trimmed, key.len) {85 return value86 }87 }88 return none89}90919293fn quoted_value(line string, from int) ?string {94 for at in from .. line.len {95 if line[at] != `"` && line[at] != `'` {96 continue97 }98 quote := line[at]99 for end in at + 1 .. line.len {100 if line[end] == quote {101 return line[at + 1..end]102 }103 }104 return none105 }106 return none107}108109110111fn plist_version(path string) ?string {112 text := os.read_file(path) or { return none }113 at := text.index('<key>CFBundleShortVersionString</key>') or { return none }114 rest := text[at..]115 open := rest.index('<string>') or { return none }116 close := rest.index('</string>') or { return none }117 if close <= open + '<string>'.len {118 return none119 }120 value := rest[open + '<string>'.len..close].trim_space()121 if value == '' {122 return none123 }124 return value125}126127128129130131132fn gradle_version(path string) ?string {133 text := os.read_file(path) or { return none }134 for line in text.split_into_lines() {135 trimmed := line.trim_left(' \t')136 if trimmed == '' || trimmed.starts_with('//') || trimmed.starts_with('/*')137 || trimmed.starts_with('*') {138 continue139 }140 if !trimmed.starts_with('versionName') || trimmed.len <= 'versionName'.len141 || (trimmed['versionName'.len] != ` ` && trimmed['versionName'.len] != `=`) {142 continue143 }144 if value := quoted_value(trimmed, 'versionName'.len) {145 return value146 }147 }148 return none149}