первичная итерация генератора из aur пакетов
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				Update alr-git / changelog (push) Failing after 25s
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	Update alr-git / changelog (push) Failing after 25s
				
			This commit is contained in:
		
							
								
								
									
										668
									
								
								internal/gen/aur.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										668
									
								
								internal/gen/aur.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,668 @@ | ||||
| // This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||
| // It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||
| // | ||||
| // ALR - Any Linux Repository | ||||
| // Copyright (C) 2025 The ALR Authors | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // (at your option) any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| package gen | ||||
|  | ||||
| import ( | ||||
| 	_ "embed" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"regexp" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
| ) | ||||
|  | ||||
| // Встраиваем шаблон для AUR пакетов | ||||
| // | ||||
| //go:embed tmpls/aur.tmpl.sh | ||||
| var aurTmpl string | ||||
|  | ||||
| // AUROptions содержит параметры для генерации шаблона AUR | ||||
| type AUROptions struct { | ||||
| 	Name    string // Имя пакета в AUR | ||||
| 	Version string // Версия пакета (опционально, если не указана - берется последняя) | ||||
| 	CreateDir bool  // Создавать ли директорию для пакета и дополнительные файлы | ||||
| } | ||||
|  | ||||
| // aurAPIResponse представляет структуру ответа от API AUR | ||||
| type aurAPIResponse struct { | ||||
| 	Version      int         `json:"version"`      // Версия API | ||||
| 	Type         string      `json:"type"`         // Тип ответа | ||||
| 	ResultCount  int         `json:"resultcount"`  // Количество результатов | ||||
| 	Results      []aurResult `json:"results"`      // Массив результатов | ||||
| 	Error        string      `json:"error"`        // Сообщение об ошибке (если есть) | ||||
| } | ||||
|  | ||||
| // aurResult содержит информацию о пакете из AUR | ||||
| type aurResult struct { | ||||
| 	ID             int      `json:"ID"` | ||||
| 	Name           string   `json:"Name"` | ||||
| 	PackageBaseID  int      `json:"PackageBaseID"` | ||||
| 	PackageBase    string   `json:"PackageBase"` | ||||
| 	Version        string   `json:"Version"` | ||||
| 	Description    string   `json:"Description"` | ||||
| 	URL            string   `json:"URL"` | ||||
| 	NumVotes       int      `json:"NumVotes"` | ||||
| 	Popularity     float64  `json:"Popularity"` | ||||
| 	OutOfDate      *int     `json:"OutOfDate"` | ||||
| 	Maintainer     string   `json:"Maintainer"` | ||||
| 	FirstSubmitted int      `json:"FirstSubmitted"` | ||||
| 	LastModified   int      `json:"LastModified"` | ||||
| 	URLPath        string   `json:"URLPath"` | ||||
| 	License        []string `json:"License"` | ||||
| 	Keywords       []string `json:"Keywords"` | ||||
| 	Depends        []string `json:"Depends"` | ||||
| 	MakeDepends    []string `json:"MakeDepends"` | ||||
| 	OptDepends     []string `json:"OptDepends"` | ||||
| 	CheckDepends   []string `json:"CheckDepends"` | ||||
| 	Conflicts      []string `json:"Conflicts"` | ||||
| 	Provides       []string `json:"Provides"` | ||||
| 	Replaces       []string `json:"Replaces"` | ||||
| 	// Дополнительные поля для данных из PKGBUILD | ||||
| 	Sources      []string `json:"-"` | ||||
| 	Checksums    []string `json:"-"` | ||||
| 	BuildFunc    string   `json:"-"` | ||||
| 	PackageFunc  string   `json:"-"` | ||||
| 	PrepareFunc  string   `json:"-"` | ||||
| 	PackageType  string   `json:"-"`  // python, go, rust, cpp, nodejs, bin, git | ||||
| 	HasDesktop   bool     `json:"-"`  // Есть ли desktop файлы | ||||
| 	HasSystemd   bool     `json:"-"`  // Есть ли systemd сервисы | ||||
| 	HasVersion   bool     `json:"-"`  // Есть ли функция version() | ||||
| 	HasScripts   []string `json:"-"`  // Дополнительные скрипты (postinstall, postremove, etc) | ||||
| 	HasPatches   bool     `json:"-"`  // Есть ли патчи | ||||
| 	Architectures []string `json:"-"` // Поддерживаемые архитектуры | ||||
| 	 | ||||
| 	// Автоматически определяемые файлы для install-* команд | ||||
| 	BinaryFiles  []string `json:"-"`  // Исполняемые файлы для install-binary | ||||
| 	LicenseFiles []string `json:"-"`  // Лицензионные файлы для install-license | ||||
| 	ManualFiles  []string `json:"-"`  // Man страницы для install-manual | ||||
| 	DesktopFiles []string `json:"-"`  // Desktop файлы для install-desktop | ||||
| 	ServiceFiles []string `json:"-"`  // Systemd сервисы для install-systemd | ||||
| 	CompletionFiles map[string]string `json:"-"` // Файлы автодополнения по типу (bash, zsh, fish) | ||||
| } | ||||
|  | ||||
| // Вспомогательные методы для шаблона | ||||
| func (r aurResult) LicenseString() string { | ||||
| 	if len(r.License) == 0 { | ||||
| 		return "custom:Unknown" | ||||
| 	} | ||||
| 	// Форматируем лицензии для alr.sh | ||||
| 	licenses := make([]string, len(r.License)) | ||||
| 	for i, l := range r.License { | ||||
| 		licenses[i] = fmt.Sprintf("'%s'", l) | ||||
| 	} | ||||
| 	return strings.Join(licenses, " ") | ||||
| } | ||||
|  | ||||
| func (r aurResult) DependsString() string { | ||||
| 	if len(r.Depends) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	deps := make([]string, len(r.Depends)) | ||||
| 	for i, d := range r.Depends { | ||||
| 		// Убираем версионные ограничения для простоты | ||||
| 		dep := strings.Split(d, ">=")[0] | ||||
| 		dep = strings.Split(dep, "<=")[0] | ||||
| 		dep = strings.Split(dep, "=")[0] | ||||
| 		dep = strings.Split(dep, ">")[0] | ||||
| 		dep = strings.Split(dep, "<")[0] | ||||
| 		deps[i] = fmt.Sprintf("'%s'", dep) | ||||
| 	} | ||||
| 	return strings.Join(deps, " ") | ||||
| } | ||||
|  | ||||
| func (r aurResult) MakeDependsString() string { | ||||
| 	if len(r.MakeDepends) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	deps := make([]string, len(r.MakeDepends)) | ||||
| 	for i, d := range r.MakeDepends { | ||||
| 		// Убираем версионные ограничения для простоты | ||||
| 		dep := strings.Split(d, ">=")[0] | ||||
| 		dep = strings.Split(dep, "<=")[0] | ||||
| 		dep = strings.Split(dep, "=")[0] | ||||
| 		dep = strings.Split(dep, ">")[0] | ||||
| 		dep = strings.Split(dep, "<")[0] | ||||
| 		deps[i] = fmt.Sprintf("'%s'", dep) | ||||
| 	} | ||||
| 	return strings.Join(deps, " ") | ||||
| } | ||||
|  | ||||
| func (r aurResult) GitURL() string { | ||||
| 	// Формируем URL для клонирования из AUR | ||||
| 	return fmt.Sprintf("https://aur.archlinux.org/%s.git", r.PackageBase) | ||||
| } | ||||
|  | ||||
| func (r aurResult) ArchitecturesString() string { | ||||
| 	if len(r.Architectures) == 0 { | ||||
| 		return "'all'" | ||||
| 	} | ||||
| 	archs := make([]string, len(r.Architectures)) | ||||
| 	for i, arch := range r.Architectures { | ||||
| 		archs[i] = fmt.Sprintf("'%s'", arch) | ||||
| 	} | ||||
| 	return strings.Join(archs, " ") | ||||
| } | ||||
|  | ||||
| func (r aurResult) OptDependsString() string { | ||||
| 	if len(r.OptDepends) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	optDeps := make([]string, 0, len(r.OptDepends)) | ||||
| 	for _, dep := range r.OptDepends { | ||||
| 		// Форматируем опциональные зависимости для alr.sh | ||||
| 		parts := strings.SplitN(dep, ": ", 2) | ||||
| 		if len(parts) == 2 { | ||||
| 			optDeps = append(optDeps, fmt.Sprintf("'%s: %s'", parts[0], parts[1])) | ||||
| 		} else { | ||||
| 			optDeps = append(optDeps, fmt.Sprintf("'%s'", dep)) | ||||
| 		} | ||||
| 	} | ||||
| 	return strings.Join(optDeps, "\n\t") | ||||
| } | ||||
|  | ||||
| func (r aurResult) ScriptsString() string { | ||||
| 	if len(r.HasScripts) == 0 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	scripts := make([]string, len(r.HasScripts)) | ||||
| 	for i, script := range r.HasScripts { | ||||
| 		scripts[i] = fmt.Sprintf("['%s']='%s.sh'", script, script) | ||||
| 	} | ||||
| 	return strings.Join(scripts, "\n\t") | ||||
| } | ||||
|  | ||||
| // GenerateInstallCommands генерирует команды install-* для шаблона | ||||
| func (r aurResult) GenerateInstallCommands() string { | ||||
| 	var commands []string | ||||
| 	 | ||||
| 	// install-binary команды | ||||
| 	for _, binary := range r.BinaryFiles { | ||||
| 		if binary == "./"+r.Name { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-binary %s", binary)) | ||||
| 		} else { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-binary %s %s", binary, r.Name)) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// install-license команды | ||||
| 	for _, license := range r.LicenseFiles { | ||||
| 		if license == "LICENSE" || license == "./LICENSE" { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name)) | ||||
| 		} else { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-license %s %s/LICENSE", license, r.Name)) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// install-manual команды | ||||
| 	for _, manual := range r.ManualFiles { | ||||
| 		commands = append(commands, fmt.Sprintf("\tinstall-manual %s", manual)) | ||||
| 	} | ||||
| 	 | ||||
| 	// install-desktop команды | ||||
| 	for _, desktop := range r.DesktopFiles { | ||||
| 		commands = append(commands, fmt.Sprintf("\tinstall-desktop %s", desktop)) | ||||
| 	} | ||||
| 	 | ||||
| 	// install-systemd команды | ||||
| 	for _, service := range r.ServiceFiles { | ||||
| 		if strings.Contains(service, "user") { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-systemd-user %s", service)) | ||||
| 		} else { | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-systemd %s", service)) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// install-completion команды | ||||
| 	for shell, file := range r.CompletionFiles { | ||||
| 		switch shell { | ||||
| 		case "bash": | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-completion bash %s < %s", r.Name, file)) | ||||
| 		case "zsh": | ||||
| 			commands = append(commands, fmt.Sprintf("\tinstall-completion zsh %s < %s", r.Name, file)) | ||||
| 		case "fish": | ||||
| 			commands = append(commands, fmt.Sprintf("\t%s completion fish | install-completion fish %s", r.Name, r.Name)) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	if len(commands) == 0 { | ||||
| 		return "\t# TODO: Добавьте команды установки файлов" | ||||
| 	} | ||||
| 	 | ||||
| 	return strings.Join(commands, "\n") | ||||
| } | ||||
|  | ||||
| // fetchPKGBUILD загружает PKGBUILD файл для пакета | ||||
| func fetchPKGBUILD(packageBase string) (string, error) { | ||||
| 	// URL для raw PKGBUILD | ||||
| 	pkgbuildURL := fmt.Sprintf("https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=%s", packageBase) | ||||
| 	 | ||||
| 	res, err := http.Get(pkgbuildURL) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to fetch PKGBUILD: %w", err) | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
| 	 | ||||
| 	if res.StatusCode != 200 { | ||||
| 		return "", fmt.Errorf("failed to fetch PKGBUILD: status %s", res.Status) | ||||
| 	} | ||||
| 	 | ||||
| 	data, err := io.ReadAll(res.Body) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to read PKGBUILD: %w", err) | ||||
| 	} | ||||
| 	 | ||||
| 	return string(data), nil | ||||
| } | ||||
|  | ||||
| // parseSources извлекает источники из PKGBUILD | ||||
| func parseSources(pkgbuild string) []string { | ||||
| 	var sources []string | ||||
| 	 | ||||
| 	// Регулярное выражение для поиска массива source | ||||
| 	// Поддерживает как однострочные, так и многострочные определения | ||||
| 	sourceRegex := regexp.MustCompile(`(?ms)source=\((.*?)\)`) | ||||
| 	matches := sourceRegex.FindStringSubmatch(pkgbuild) | ||||
| 	 | ||||
| 	if len(matches) > 1 { | ||||
| 		// Извлекаем содержимое массива source | ||||
| 		sourceContent := matches[1] | ||||
| 		 | ||||
| 		// Разбираем элементы массива | ||||
| 		// Учитываем кавычки и переносы строк | ||||
| 		elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) | ||||
| 		elements := elemRegex.FindAllStringSubmatch(sourceContent, -1) | ||||
| 		 | ||||
| 		for _, elem := range elements { | ||||
| 			if len(elem) > 1 { | ||||
| 				source := elem[1] | ||||
| 				// Заменяем переменные версии | ||||
| 				source = strings.ReplaceAll(source, "$pkgver", "${version}") | ||||
| 				source = strings.ReplaceAll(source, "${pkgver}", "${version}") | ||||
| 				source = strings.ReplaceAll(source, "$pkgname", "${name}") | ||||
| 				source = strings.ReplaceAll(source, "${pkgname}", "${name}") | ||||
| 				// Обрабатываем другие переменные (упрощенно) | ||||
| 				source = strings.ReplaceAll(source, "$_commit", "${_commit}") | ||||
| 				source = strings.ReplaceAll(source, "${_commit}", "${_commit}") | ||||
| 				sources = append(sources, source) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Если источники не найдены в source=(), проверяем source_x86_64 и другие архитектуры | ||||
| 	if len(sources) == 0 { | ||||
| 		archSourceRegex := regexp.MustCompile(`(?ms)source_(?:x86_64|aarch64)=\((.*?)\)`) | ||||
| 		matches = archSourceRegex.FindStringSubmatch(pkgbuild) | ||||
| 		if len(matches) > 1 { | ||||
| 			sourceContent := matches[1] | ||||
| 			elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) | ||||
| 			elements := elemRegex.FindAllStringSubmatch(sourceContent, -1) | ||||
| 			 | ||||
| 			for _, elem := range elements { | ||||
| 				if len(elem) > 1 { | ||||
| 					source := elem[1] | ||||
| 					source = strings.ReplaceAll(source, "$pkgver", "${version}") | ||||
| 					source = strings.ReplaceAll(source, "${pkgver}", "${version}") | ||||
| 					source = strings.ReplaceAll(source, "$pkgname", "${name}") | ||||
| 					source = strings.ReplaceAll(source, "${pkgname}", "${name}") | ||||
| 					sources = append(sources, source) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	return sources | ||||
| } | ||||
|  | ||||
| // parseChecksums извлекает контрольные суммы из PKGBUILD | ||||
| func parseChecksums(pkgbuild string) []string { | ||||
| 	var checksums []string | ||||
| 	 | ||||
| 	// Пробуем разные типы контрольных сумм | ||||
| 	for _, hashType := range []string{"sha256sums", "sha512sums", "sha1sums", "md5sums", "b2sums"} { | ||||
| 		regex := regexp.MustCompile(fmt.Sprintf(`(?ms)%s=\((.*?)\)`, hashType)) | ||||
| 		matches := regex.FindStringSubmatch(pkgbuild) | ||||
| 		 | ||||
| 		if len(matches) > 1 { | ||||
| 			content := matches[1] | ||||
| 			elemRegex := regexp.MustCompile(`['"]([^'"]+)['"]`) | ||||
| 			elements := elemRegex.FindAllStringSubmatch(content, -1) | ||||
| 			 | ||||
| 			for _, elem := range elements { | ||||
| 				if len(elem) > 1 { | ||||
| 					checksums = append(checksums, elem[1]) | ||||
| 				} | ||||
| 			} | ||||
| 			 | ||||
| 			if len(checksums) > 0 { | ||||
| 				break // Используем первый найденный тип хешей | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	return checksums | ||||
| } | ||||
|  | ||||
| // parseFunctions извлекает функции build(), package() и prepare() из PKGBUILD | ||||
| func parseFunctions(pkgbuild string) (buildFunc, packageFunc, prepareFunc string) { | ||||
| 	// Извлекаем функцию build() | ||||
| 	buildRegex := regexp.MustCompile(`(?ms)^build\(\)\s*\{(.*?)^\}`) | ||||
| 	if matches := buildRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { | ||||
| 		buildFunc = strings.TrimSpace(matches[1]) | ||||
| 	} | ||||
| 	 | ||||
| 	// Извлекаем функцию package() | ||||
| 	packageRegex := regexp.MustCompile(`(?ms)^package\(\)\s*\{(.*?)^\}`) | ||||
| 	if matches := packageRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { | ||||
| 		packageFunc = strings.TrimSpace(matches[1]) | ||||
| 	} | ||||
| 	 | ||||
| 	// Извлекаем функцию prepare() | ||||
| 	prepareRegex := regexp.MustCompile(`(?ms)^prepare\(\)\s*\{(.*?)^\}`) | ||||
| 	if matches := prepareRegex.FindStringSubmatch(pkgbuild); len(matches) > 1 { | ||||
| 		prepareFunc = strings.TrimSpace(matches[1]) | ||||
| 	} | ||||
| 	 | ||||
| 	return buildFunc, packageFunc, prepareFunc | ||||
| } | ||||
|  | ||||
| // detectInstallableFiles анализирует PKGBUILD и определяет файлы для install-* команд | ||||
| func detectInstallableFiles(pkg *aurResult, pkgbuild string) { | ||||
| 	// Инициализируем карту для файлов автодополнения | ||||
| 	pkg.CompletionFiles = make(map[string]string) | ||||
| 	 | ||||
| 	// Для простоты, добавляем стандартные файлы для типа пакета | ||||
| 	switch pkg.PackageType { | ||||
| 	case "go": | ||||
| 		pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) | ||||
| 	case "rust": | ||||
| 		pkg.BinaryFiles = append(pkg.BinaryFiles, "./target/release/"+pkg.Name) | ||||
| 	case "cpp", "meson": | ||||
| 		pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) // обычно в корне после сборки | ||||
| 	case "bin": | ||||
| 		pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) | ||||
| 	default: | ||||
| 		if pkg.PackageType != "python" && pkg.PackageType != "nodejs" { | ||||
| 			pkg.BinaryFiles = append(pkg.BinaryFiles, "./"+pkg.Name) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Ищем лицензионные файлы для install-license с более точными паттернами | ||||
| 	licenseRegex := regexp.MustCompile(`(?i)\b(LICENSE|COPYING|COPYRIGHT|LICENCE)(?:\.[a-zA-Z0-9]+)?\b`) | ||||
| 	licenseMatches := licenseRegex.FindAllString(pkgbuild, -1) | ||||
| 	for _, match := range licenseMatches { | ||||
| 		// Фильтруем только реальные файлы лицензий | ||||
| 		if strings.Contains(strings.ToLower(match), "license") ||  | ||||
| 		   strings.Contains(strings.ToLower(match), "copying") ||  | ||||
| 		   strings.Contains(strings.ToLower(match), "copyright") { | ||||
| 			if !contains(pkg.LicenseFiles, "./"+match) { | ||||
| 				pkg.LicenseFiles = append(pkg.LicenseFiles, "./"+match) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Если не найдены лицензионные файлы, добавляем стандартные | ||||
| 	if len(pkg.LicenseFiles) == 0 { | ||||
| 		pkg.LicenseFiles = append(pkg.LicenseFiles, "LICENSE") | ||||
| 	} | ||||
| 	 | ||||
| 	// Ищем man страницы для install-manual с более точными паттернами | ||||
| 	manRegex := regexp.MustCompile(`\b\w+\.(?:1|2|3|4|5|6|7|8)(?:\.gz)?\b`) | ||||
| 	manMatches := manRegex.FindAllString(pkgbuild, -1) | ||||
| 	for _, match := range manMatches { | ||||
| 		// Проверяем, что это не переменная или часть кода | ||||
| 		if !strings.Contains(match, "$") && !strings.Contains(match, "{") { | ||||
| 			if !contains(pkg.ManualFiles, "./"+match) { | ||||
| 				pkg.ManualFiles = append(pkg.ManualFiles, "./"+match) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Ищем desktop файлы для install-desktop | ||||
| 	desktopRegex := regexp.MustCompile(`[^/\s]*\.desktop`) | ||||
| 	desktopMatches := desktopRegex.FindAllString(pkgbuild, -1) | ||||
| 	for _, match := range desktopMatches { | ||||
| 		if !contains(pkg.DesktopFiles, "./"+match) { | ||||
| 			pkg.DesktopFiles = append(pkg.DesktopFiles, "./"+match) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Ищем systemd сервисы для install-systemd | ||||
| 	serviceRegex := regexp.MustCompile(`[^/\s]*\.service`) | ||||
| 	serviceMatches := serviceRegex.FindAllString(pkgbuild, -1) | ||||
| 	for _, match := range serviceMatches { | ||||
| 		if !contains(pkg.ServiceFiles, "./"+match) { | ||||
| 			pkg.ServiceFiles = append(pkg.ServiceFiles, "./"+match) | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Ищем файлы автодополнения | ||||
| 	completionPatterns := map[string]string{ | ||||
| 		"bash": `completions?/.*\.bash|bash-completion`, | ||||
| 		"zsh":  `completions?/.*\.zsh|zsh.*completion`, | ||||
| 		"fish": `completions?/.*\.fish|fish.*completion`, | ||||
| 	} | ||||
| 	 | ||||
| 	for shell, pattern := range completionPatterns { | ||||
| 		regex := regexp.MustCompile(fmt.Sprintf(`(?i)%s`, pattern)) | ||||
| 		matches := regex.FindAllString(pkgbuild, -1) | ||||
| 		if len(matches) > 0 { | ||||
| 			pkg.CompletionFiles[shell] = matches[0] | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // contains проверяет, содержит ли слайс строк указанную строку | ||||
| func contains(slice []string, item string) bool { | ||||
| 	for _, s := range slice { | ||||
| 		if s == item { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // detectPackageType определяет тип пакета на основе имени, зависимостей и источников | ||||
| func detectPackageType(pkg *aurResult, pkgbuild string) { | ||||
| 	name := strings.ToLower(pkg.Name) | ||||
| 	 | ||||
| 	// Определяем тип на основе имени пакета | ||||
| 	switch { | ||||
| 	case strings.HasPrefix(name, "python") || strings.HasPrefix(name, "python3-"): | ||||
| 		pkg.PackageType = "python" | ||||
| 	case strings.Contains(name, "nodejs") || strings.Contains(name, "node-"): | ||||
| 		pkg.PackageType = "nodejs" | ||||
| 	case strings.HasSuffix(name, "-bin"): | ||||
| 		pkg.PackageType = "bin" | ||||
| 	case strings.HasSuffix(name, "-git"): | ||||
| 		pkg.PackageType = "git" | ||||
| 		pkg.HasVersion = true // Git пакеты обычно имеют функцию version() | ||||
| 	case strings.Contains(name, "rust") || hasRustSources(pkg.Sources): | ||||
| 		pkg.PackageType = "rust" | ||||
| 	case strings.Contains(name, "go-") || hasGoSources(pkg.Sources): | ||||
| 		pkg.PackageType = "go" | ||||
| 	case strings.Contains(name, "-rust") || strings.Contains(name, "paru") || strings.Contains(name, "cargo-"): | ||||
| 		pkg.PackageType = "rust" | ||||
| 	default: | ||||
| 		// Определяем по зависимостям сборки | ||||
| 		for _, dep := range pkg.MakeDepends { | ||||
| 			depLower := strings.ToLower(dep) | ||||
| 			switch { | ||||
| 			case strings.Contains(depLower, "meson") || strings.Contains(depLower, "ninja"): | ||||
| 				pkg.PackageType = "meson" | ||||
| 			case strings.Contains(depLower, "cmake") || strings.Contains(depLower, "gcc") || strings.Contains(depLower, "clang"): | ||||
| 				pkg.PackageType = "cpp" | ||||
| 			case strings.Contains(depLower, "python"): | ||||
| 				pkg.PackageType = "python" | ||||
| 			case strings.Contains(depLower, "go"): | ||||
| 				pkg.PackageType = "go" | ||||
| 			case strings.Contains(depLower, "rust") || strings.Contains(depLower, "cargo"): | ||||
| 				pkg.PackageType = "rust" | ||||
| 			case strings.Contains(depLower, "npm") || strings.Contains(depLower, "nodejs"): | ||||
| 				pkg.PackageType = "nodejs" | ||||
| 			} | ||||
| 			if pkg.PackageType != "" { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// Определяем архитектуры на основе типа пакета | ||||
| 	if pkg.PackageType == "bin" { | ||||
| 		pkg.Architectures = []string{"amd64"} // Бинарные пакеты обычно специфичны для архитектуры | ||||
| 	} else { | ||||
| 		pkg.Architectures = []string{"all"} // Исходный код собирается для любой архитектуры | ||||
| 	} | ||||
| 	 | ||||
| 	// Определяем наличие desktop файлов | ||||
| 	pkg.HasDesktop = strings.Contains(pkgbuild, ".desktop") ||  | ||||
| 		strings.Contains(pkgbuild, "install-desktop") || | ||||
| 		strings.Contains(pkgbuild, "xdg-desktop") | ||||
| 	 | ||||
| 	// Определяем наличие systemd сервисов | ||||
| 	pkg.HasSystemd = strings.Contains(pkgbuild, ".service") || | ||||
| 		strings.Contains(pkgbuild, "systemctl") || | ||||
| 		strings.Contains(pkgbuild, "install-systemd") | ||||
| 	 | ||||
| 	// Определяем наличие функции version() для -git пакетов | ||||
| 	pkg.HasVersion = strings.Contains(pkgbuild, "pkgver()") ||  | ||||
| 		(strings.HasSuffix(name, "-git") && strings.Contains(pkgbuild, "git describe")) | ||||
| 	 | ||||
| 	// Определяем наличие патчей | ||||
| 	pkg.HasPatches = strings.Contains(pkgbuild, "patch ") ||  | ||||
| 		strings.Contains(pkgbuild, ".patch") || | ||||
| 		strings.Contains(pkgbuild, ".diff") | ||||
| 	 | ||||
| 	// Определяем дополнительные скрипты | ||||
| 	if strings.Contains(pkgbuild, "post_install") { | ||||
| 		pkg.HasScripts = append(pkg.HasScripts, "postinstall") | ||||
| 	} | ||||
| 	if strings.Contains(pkgbuild, "pre_remove") || strings.Contains(pkgbuild, "post_remove") { | ||||
| 		pkg.HasScripts = append(pkg.HasScripts, "postremove") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // hasRustSources проверяет, содержат ли источники Rust проекты | ||||
| func hasRustSources(sources []string) bool { | ||||
| 	for _, src := range sources { | ||||
| 		if strings.Contains(src, "crates.io") || strings.Contains(src, "Cargo.toml") { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // hasGoSources проверяет, содержат ли источники Go проекты | ||||
| func hasGoSources(sources []string) bool { | ||||
| 	for _, src := range sources { | ||||
| 		if strings.Contains(src, "github.com") && strings.Contains(src, "/go") { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // AUR генерирует шаблон alr.sh на основе пакета из AUR | ||||
| func AUR(w io.Writer, opts AUROptions) error { | ||||
| 	// Создаем шаблон с функциями | ||||
| 	tmpl, err := template.New("aur"). | ||||
| 		Funcs(funcs). | ||||
| 		Parse(aurTmpl) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	// Формируем URL запроса к AUR API | ||||
| 	apiURL := "https://aur.archlinux.org/rpc/v5/info" | ||||
| 	params := url.Values{} | ||||
| 	params.Add("arg[]", opts.Name) | ||||
| 	fullURL := fmt.Sprintf("%s?%s", apiURL, params.Encode()) | ||||
|  | ||||
| 	// Выполняем запрос к AUR API | ||||
| 	res, err := http.Get(fullURL) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to fetch AUR package info: %w", err) | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
|  | ||||
| 	if res.StatusCode != 200 { | ||||
| 		return fmt.Errorf("AUR API returned status: %s", res.Status) | ||||
| 	} | ||||
|  | ||||
| 	// Декодируем ответ | ||||
| 	var resp aurAPIResponse | ||||
| 	err = json.NewDecoder(res.Body).Decode(&resp) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to decode AUR response: %w", err) | ||||
| 	} | ||||
|  | ||||
| 	// Проверяем наличие ошибки в ответе | ||||
| 	if resp.Error != "" { | ||||
| 		return fmt.Errorf("AUR API error: %s", resp.Error) | ||||
| 	} | ||||
|  | ||||
| 	// Проверяем, что пакет найден | ||||
| 	if resp.ResultCount == 0 { | ||||
| 		return fmt.Errorf("package '%s' not found in AUR", opts.Name) | ||||
| 	} | ||||
|  | ||||
| 	// Берем первый результат | ||||
| 	pkg := resp.Results[0] | ||||
|  | ||||
| 	// Если указана версия, проверяем соответствие | ||||
| 	if opts.Version != "" && pkg.Version != opts.Version { | ||||
| 		// Предупреждаем, но продолжаем с актуальной версией из AUR | ||||
| 		fmt.Fprintf(w, "# WARNING: Requested version %s, but AUR has %s\n", opts.Version, pkg.Version) | ||||
| 	} | ||||
|  | ||||
| 	// Загружаем PKGBUILD для получения источников | ||||
| 	pkgbuild, err := fetchPKGBUILD(pkg.PackageBase) | ||||
| 	if err != nil { | ||||
| 		// Если не удалось загрузить PKGBUILD, используем fallback на AUR репозиторий | ||||
| 		fmt.Fprintf(w, "# WARNING: Could not fetch PKGBUILD: %v\n", err) | ||||
| 		fmt.Fprintf(w, "# Using AUR repository as source\n") | ||||
| 		pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())} | ||||
| 		pkg.Checksums = []string{"SKIP"} | ||||
| 	} else { | ||||
| 		// Извлекаем источники из PKGBUILD | ||||
| 		pkg.Sources = parseSources(pkgbuild) | ||||
| 		pkg.Checksums = parseChecksums(pkgbuild) | ||||
| 		pkg.BuildFunc, pkg.PackageFunc, pkg.PrepareFunc = parseFunctions(pkgbuild) | ||||
| 		 | ||||
| 		// Определяем тип пакета | ||||
| 		detectPackageType(&pkg, pkgbuild) | ||||
| 		 | ||||
| 		// Определяем файлы для install-* команд | ||||
| 		detectInstallableFiles(&pkg, pkgbuild) | ||||
| 		 | ||||
| 		// Если источники не найдены, используем fallback | ||||
| 		if len(pkg.Sources) == 0 { | ||||
| 			fmt.Fprintf(w, "# WARNING: No sources found in PKGBUILD\n") | ||||
| 			fmt.Fprintf(w, "# Using AUR repository as source\n") | ||||
| 			pkg.Sources = []string{fmt.Sprintf("%s::git+%s", pkg.Name, pkg.GitURL())} | ||||
| 			pkg.Checksums = []string{"SKIP"} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Выполняем шаблон | ||||
| 	return tmpl.Execute(w, pkg) | ||||
| } | ||||
							
								
								
									
										133
									
								
								internal/gen/tmpls/aur.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								internal/gen/tmpls/aur.tmpl.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| # This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan. | ||||
| # It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors. | ||||
| # | ||||
| # ALR - Any Linux Repository | ||||
| # Copyright (C) 2025 The ALR Authors | ||||
| # | ||||
| # This program is free software: you can redistribute it and/or modify | ||||
| # it under the terms of the GNU General Public License as published by | ||||
| # the Free Software Foundation, either version 3 of the License, or | ||||
| # (at your option) any later version. | ||||
| # | ||||
| # This program is distributed in the hope that it will be useful, | ||||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
| # GNU General Public License for more details. | ||||
| # | ||||
| # You should have received a copy of the GNU General Public License | ||||
| # along with this program.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  | ||||
| # Generated from AUR package: {{.Name}} | ||||
| # Package type: {{.PackageType}} | ||||
| # AUR votes: {{.NumVotes}} | Popularity: {{printf "%.2f" .Popularity}} | ||||
| # Original maintainer: {{.Maintainer}} | ||||
| # Adapted for ALR by automation | ||||
|  | ||||
| name='{{.Name}}' | ||||
| version='{{.Version}}' | ||||
| release='1' | ||||
| desc='{{.Description}}' | ||||
| {{if ne .Description ""}}desc_ru='{{.Description}}'{{end}} | ||||
| homepage='{{.URL}}' | ||||
| maintainer="Евгений Храмов <xpamych@yandex.ru> (imported from AUR)" | ||||
| {{if ne .Description ""}}maintainer_ru="Евгений Храмов <xpamych@yandex.ru> (импортирован из AUR)"{{end}} | ||||
| architectures=({{.ArchitecturesString}}) | ||||
| license=({{.LicenseString}}) | ||||
| {{if .Provides}}provides=({{range .Provides}}'{{.}}' {{end}}){{end}} | ||||
| {{if .Conflicts}}conflicts=({{range .Conflicts}}'{{.}}' {{end}}){{end}} | ||||
| {{if .Replaces}}replaces=({{range .Replaces}}'{{.}}' {{end}}){{end}} | ||||
|  | ||||
| # Базовые зависимости | ||||
| {{if .DependsString}}deps=({{.DependsString}}){{else}}deps=(){{end}} | ||||
| {{if .MakeDependsString}}build_deps=({{.MakeDependsString}}){{else}}build_deps=(){{end}} | ||||
|  | ||||
| # Зависимости для конкретных дистрибутивов (адаптируйте под нужды пакета) | ||||
| {{if .DependsString}}deps_arch=({{.DependsString}}) | ||||
| deps_debian=({{.DependsString}}) | ||||
| deps_altlinux=({{.DependsString}}) | ||||
| deps_alpine=({{.DependsString}}){{end}} | ||||
|  | ||||
| {{if and .MakeDependsString (ne .PackageType "bin")}}# Зависимости сборки для конкретных дистрибутивов | ||||
| build_deps_arch=({{.MakeDependsString}}) | ||||
| build_deps_debian=({{.MakeDependsString}}) | ||||
| build_deps_altlinux=({{.MakeDependsString}}) | ||||
| build_deps_alpine=({{.MakeDependsString}}){{end}} | ||||
|  | ||||
| {{if .OptDependsString}}# Опциональные зависимости | ||||
| opt_deps=( | ||||
| 	{{.OptDependsString}} | ||||
| ){{end}} | ||||
|  | ||||
| # Источники из PKGBUILD | ||||
| sources=({{range .Sources}}"{{.}}" {{end}}) | ||||
| checksums=({{range .Checksums}}'{{.}}' {{end}}) | ||||
|  | ||||
| {{if .HasVersion}}# Функция версии для Git-пакетов | ||||
| version() { | ||||
| 	cd "$srcdir/{{.Name}}" | ||||
| 	git-version | ||||
| } | ||||
| {{end}} | ||||
|  | ||||
| {{if .ScriptsString}}# Дополнительные скрипты | ||||
| scripts=( | ||||
| 	{{.ScriptsString}} | ||||
| ){{end}} | ||||
|  | ||||
| {{if or .PrepareFunc .HasPatches}}prepare() { | ||||
| 	cd "$srcdir"{{if .PrepareFunc}} | ||||
| 	# Из PKGBUILD: | ||||
| 	{{.PrepareFunc}}{{else}} | ||||
| 	# Применение патчей и подготовка исходников | ||||
| 	# Раскомментируйте и адаптируйте при необходимости: | ||||
| 	# patch -p1 < "${scriptdir}/fix.patch"{{end}} | ||||
| }{{else}}# prepare() { | ||||
| # 	cd "$srcdir" | ||||
| # 	# Применение патчей и подготовка исходников при необходимости | ||||
| # 	# patch -p1 < "${scriptdir}/fix.patch" | ||||
| # }{{end}} | ||||
|  | ||||
| {{if ne .PackageType "bin"}}build() { | ||||
| 	cd "$srcdir"{{if .BuildFunc}} | ||||
| 	# Из PKGBUILD: | ||||
| 	{{.BuildFunc}}{{else}} | ||||
| 	 | ||||
| 	# TODO: Адаптируйте команды сборки под конкретный проект ({{.PackageType}}) | ||||
| 	{{if eq .PackageType "meson"}}# Для Meson проектов: | ||||
| 	meson setup build --prefix=/usr --buildtype=release | ||||
| 	ninja -C build -j $(nproc){{else if eq .PackageType "cpp"}}# Для C/C++ проектов: | ||||
| 	mkdir -p build && cd build | ||||
| 	cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr | ||||
| 	make -j$(nproc){{else if eq .PackageType "go"}}# Для Go проектов: | ||||
| 	go build -buildmode=pie -trimpath -ldflags "-s -w" -o {{.Name}}{{else if eq .PackageType "python"}}# Для Python проектов: | ||||
| 	python -m build --wheel --no-isolation{{else if eq .PackageType "nodejs"}}# Для Node.js проектов: | ||||
| 	npm ci --production | ||||
| 	npm run build{{else if eq .PackageType "rust"}}# Для Rust проектов: | ||||
| 	cargo build --release --locked{{else if eq .PackageType "git"}}# Для Git проектов (обычно исходный код): | ||||
| 	make -j$(nproc){{else}}# Стандартная сборка: | ||||
| 	make -j$(nproc){{end}}{{end}} | ||||
| }{{else}}# Бинарный пакет - сборка не требуется{{end}} | ||||
|  | ||||
| package() { | ||||
| 	cd "$srcdir"{{if .PackageFunc}} | ||||
| 	# Из PKGBUILD (адаптировано для ALR): | ||||
| 	{{.PackageFunc}} | ||||
| 	 | ||||
| 	# Автоматически сгенерированные команды установки: | ||||
| {{.GenerateInstallCommands}}{{else}} | ||||
| 	 | ||||
| 	# TODO: Адаптируйте установку файлов под конкретный проект {{.Name}} | ||||
| 	{{if eq .PackageType "meson"}}# Для Meson проектов: | ||||
| 	meson install -C build --destdir="$pkgdir"{{else if eq .PackageType "cpp"}}# Для C/C++ проектов: | ||||
| 	cd build | ||||
| 	make DESTDIR="$pkgdir" install{{else if eq .PackageType "go"}}# Для Go проектов: | ||||
| 	# Исполняемый файл уже собран в корне{{else if eq .PackageType "python"}}# Для Python проектов: | ||||
| 	pip install --root="$pkgdir/" . --no-deps --disable-pip-version-check{{else if eq .PackageType "nodejs"}}# Для Node.js проектов: | ||||
| 	npm install -g --prefix="$pkgdir/usr" .{{else if eq .PackageType "rust"}}# Для Rust проектов: | ||||
| 	# Исполняемый файл в target/release/{{else if eq .PackageType "bin"}}# Бинарный пакет: | ||||
| 	# Файлы уже распакованы{{else}}# Стандартная установка: | ||||
| 	make DESTDIR="$pkgdir" install{{end}} | ||||
| 	 | ||||
| 	# Автоматически сгенерированные команды установки: | ||||
| {{.GenerateInstallCommands}}{{end}} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user