Nimbin[12]?Linux From Scratch / Packagemanager-LFS-PackageUser-System / OLD/blfs_parse_page

Packagemanager-LFS-PackageUser-System git · main

Package-user based package manager for LFS/BLFS

lfs blfs package-manager bash · first commit 2026-08-18 · last commit 2026-10-26 (8 days ago) · synced 8 hours ago · upstream: github.com/nimbin2/Packagemanager-LFS-PackageUser-System

Shell 63.7% Markdown 35.8%
git clone https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System.gitwget https://git.christianimmanuel.de/linux-from-scratch/Packagemanager-LFS-PackageUser-System/archive/Packagemanager-LFS-PackageUser-System.tar.gz
OLD/blfs_parse_page 22.5 KB · 542 lines raw
#!/usr/bin/python3

##### just do wget if pkg is not there
##### remove pkg_dir if it exist before unpacking tar



import argparse
import requests
from bs4 import BeautifulSoup, Tag
from urllib.parse import urlparse, urlsplit, unquote
import re
import os
import sys
import subprocess

# Global variable to hold the BeautifulSoup object
soup = None


soup = None
extract_name = None
extract_name_version = None
is_module = False

def fetch_webpage(url):
    global soup, extract_name, extract_name_version, is_module
    
    # If there is a fragment in the URL, use it to find the specific div.sect2
    if is_module:
        extract_name = is_module

        # Send a GET request to the URL without the fragment
        url_without_fragment = url.split("#")[0]
        response = requests.get(url_without_fragment)
        
        if response.status_code == 200:
            soup = BeautifulSoup(response.text, "html.parser")
            
            # Search for the specific div.sect2 using the criteria you mentioned
            soup = soup.select_one(f'div.sect2 h2.sect2 a[name="{is_module}"]').find_parent('div', class_='sect2')
            
            # Check if there's an h2.sect2 > a#asciidoc within the div.sect2
        else:
            print(f"Failed to retrieve the webpage. Status code: {response.status_code}")
    else:
        # Handle the case where there's no fragment in the URL (your original logic)
        response = requests.get(url)
        
        if response.status_code == 200:
            soup = BeautifulSoup(response.text, "html.parser")
        else:
            print(f"Failed to retrieve the webpage. Status code: {response.status_code}")


def strip_package_name_full(name):
    return re.sub("\(.*","",name).strip()

def extractPackageNameFull():
    global soup
    # Find the div element with class "sect1" followed by h1 with class "sect1"
    div_element = None
    if is_module:
        div_element = soup.select_one('h2.sect2')
    else:
        div_element = soup.select_one('h1.sect1')

    if div_element:
        # Extract and return the content within the div
        content = div_element.get_text().strip()
        return strip_package_name_full(content)
    else:
        print(f"extractPackageNameFull: nothing found")
        return ""


def extractPackageName():
    global soup
    name_link = None
    if is_module:
        name_link = soup.select_one("h2.sect2 > a")
    else:
        name_link = soup.select_one("h1.sect1 > a")
    name = name_link['name']
    parts = name.split("-")
    if len(parts) > 1 and parts[-1].replace(".","").isdigit():
        return "-".join(parts[:-1])
    else:
        return name

def extractPackageNameVersion(package_string):
    version_pattern = r'-(\d+(\.\d+)+)$'
    match = re.search(version_pattern, package_string)

    if match:
        package_name_without_version = package_string[:match.start()]
        package_version = match.group(1)
        return package_name_without_version, package_version
    else:
        return package_string, None

def extract_package_link():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        # Find all <a class="ulink"> elements within the div
        ulink_elements = package_div.select('div.itemizedlist a.ulink')

        if ulink_elements:
            for ulink_element in ulink_elements:
                href = ulink_element.get("href")
                if href and href.startswith("https"):
                    return href

            # If no HTTPS link is found, return the first one
            first_href = ulink_elements[0].get("href")
            return first_href

    print(f"extract_package_link: nothing found")
    return ""

def extract_package_md5():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        itemizedlists = package_div.find_all("div", class_="itemizedlist")
        md5_sum = ""

        for itemizedlist in itemizedlists:
            md5_p = itemizedlist.find("p", string=re.compile(r'\sDownload MD5 sum:\s*', re.IGNORECASE))
            if md5_p:
                # Extract the text after "*Download MD5 sum:*" and remove any line breaks or leading/trailing spaces
                md5_sum = md5_p.get_text()
                md5_sum = re.sub(r'Download MD5 sum:', '', md5_sum, flags=re.IGNORECASE).strip().replace('\n', ' ')

                # Break after finding the first matching element
                break

        return md5_sum
    else:
        print(f"extract_package_md5: nothing found")
        return None

def extract_additional_downloads():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        content = []
        h3_additional_downloads = package_div.find("h3", string=re.compile(r'\s*Additional Downloads\s*'))
        h3_dependencies = package_div.find("h3", string=re.compile(r'\s* Dependencies\s*'))


        #if h3_additional_downloads and h3_dependencies:
        if h3_additional_downloads:
            ulink_elements = []
            content = []

            # Iterate through siblings of h3_additional_downloads until h3_dependencies is found
            current_element = h3_additional_downloads.find_next_sibling()
            while current_element and current_element != h3_dependencies:
                # Find all <a> elements with class "ulink" within current_element
                ulinks = current_element.find_all("a", class_="ulink")
                for ulink in ulinks:
                    link = ulink.get("href")
                    if link:
                        content.append(link)
                current_element = current_element.find_next_sibling()

            return content
        else:
            print(f"extract_additional_downloads: nothing found")
            return []
    else:
        print(f"extract_additional_downloads: nothing found")
        return []



def extract_package_info():
    global soup
    # Find the element with class "package"
    package_element = soup.find(class_="package")

    if package_element:
        # Find all p elements between "sect2" and the h3 element with content "Package Information"
        sect2_element = None
        if is_module:
            sect2_element = package_element.find(class_="sect3")
        else:
            sect2_element = package_element.find(class_="sect2")

        if sect2_element:
            package_info = []
            p_content = ""
            break_div = "h3"
            if is_module:
                break_div = "h4"

            for element in sect2_element.next_siblings:
                if element.name == break_div and "Package Information" in element.text:
                    break
                if element.name == "p":
                    p_content = p_content + '\\n\\n' + ' '.join(line.strip() for line in element.text.strip().splitlines())

            package_info.append(p_content.replace('"', '\\"'))
            # Return the extracted package information as a single string
            extracted_info = '\n'.join(package_info).lstrip("\\n")
            return extracted_info
        else:
            print(f"extract_package_info: nothing found")
    else:
        print(f"extract_package_info: nothing found")
        return ""

def extract_package_required_pks():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        # Find all <a class="xref"> elements within <p class="required"> under the package_div
        required_xref_elements = package_div.select('p.required a.xref')

        # Extract and join the titles of all found xref elements with a comma
        titles = [xref.get("title").replace(" ","-") for xref in required_xref_elements if xref.get("title")]

        if titles:
            return titles
        else:
            print(f"extract_package_required_pks: nothing found")
            return []
    else:
        print(f"extract_package_required_pks: nothing found")
        return []

def extract_package_recommended_pks():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        # Find all <a class="xref"> elements within <p class="required"> under the package_div
        required_xref_elements = package_div.select('p.recommended a.xref')

        # Extract and join the titles of all found xref elements with a comma
        titles = [xref.get("title").replace(" ","-") for xref in required_xref_elements if xref.get("title")]

        if titles:
            return titles
        else:
            return []
            print(f"extract_package_recommended_pks: nothing found")
    else:
        print(f"extract_package_recommended_pks: nothing found")
        return []

def extract_package_optional_pks():
    global soup
    # Find the div element with class "package"
    package_div = soup.find(class_="package")

    if package_div:
        # Find all <a class="xref"> elements within <p class="required"> under the package_div
        required_xref_elements = package_div.select('p.optional a.xref')

        # Extract and join the titles of all found xref elements with a comma
        titles = [xref.get("title").replace(" ","-") for xref in required_xref_elements if xref.get("title")]

        if titles:
            return titles
        else:
            print(f"extract_package_optional_pks: nothing found")
            return []
    else:
        print(f"extract_package_optional_pks: nothing found")
        return []

def extract_package_installed_content():
    global soup
    # Find all div.seg within div.sect1 > div.content > div.seglistitem
    seglistitems = None
    if is_module:
        seglistitems = soup.select('div.content div.seglistitem')
    else:
        seglistitems = soup.select('div.sect1 > div.content div.seglistitem')

    content_dict = {}

    for seglistitem in seglistitems:
        segs = seglistitem.find_all("div", class_="seg")

        for seg in segs:
            segtitle = seg.find("strong", class_="segtitle")
            segbody = seg.find("span", class_="segbody")

            if segtitle and segbody:
                segtitle_text = segtitle.get_text().strip().lower().replace(" ", "_").replace(":","")
                segbody_text = ' '.join(segbody.get_text().split())  # Remove multiple spaces and newlines

                content_dict[segtitle_text] = segbody_text.replace(" and "," ")

    return content_dict

def extract_package_commands(class_names):
    global soup
    # Find the div element with class "installation"
    #command_div = soup.find(class_=class_names)
    command_div = soup.select(class_names + " >*")

    if command_div:
        contents = []

        # Iterate through the elements within div.installation
        for element in command_div:
            if element.name == "h2":
                d_content = "\n\n\n#### " + ''.join(line.strip() for line in element.get_text().splitlines()).replace('\n', '') + "\n"
                contents.append(d_content)
            elif element.name == "h3":
                d_content = "\n\n### " + ''.join(line.strip() for line in element.get_text().splitlines()).replace('\n', '')
                contents.append(d_content)
            elif element.name == "h4":
                d_content = "\n\n## " + ''.join(line.strip() for line in element.get_text().splitlines()).replace('\n', '')
                contents.append(d_content)
            elif element.name == "p":
                # If it's a <p> element, add its text content with "# " prefix
                p_content = ' '.join(line.strip() for line in element.get_text().splitlines()).replace('\n', ' ')
                p_content = "\n\n# " + p_content.strip() + "\n"
                contents.append(p_content)
            elif element.name == "pre":
                kbd_command = element.find("kbd", class_="command")
                if kbd_command:
                    # If it's a <pre><kbd class="command"> element, add its text content
                    contents.append(kbd_command.get_text().strip())
            elif element.name == "div":
                element_classes = element.get("class", [])
                if isinstance(element, Tag) and "admon" in element.get("class", []):
                    element_classes.remove("admon")
                    contents.append("\n#### " + ''.join([element.upper() for element in element_classes]) + " ####")
                    for element_admon in element:
                        if element_admon.name == "p":
                            lines = []
                            # Iterate through the contents of the p element
                            for item in element_admon.contents:
                                if isinstance(item, str):
                                    # If it's a text node (not a child element), strip leading and trailing whitespace
                                    line = re.sub(r'\s+', ' ', item.strip())
                                    if line:
                                        lines.append(line)
                                elif item.name:  # If it's a child element (has a name)
                                    # Add the content of the child element without leading/trailing whitespace
                                    child_text = item.get_text().strip()
                                    child_text  = re.sub(r'\s+', ' ', child_text)
                                    if child_text:
                                        lines.append(child_text)

                            # Join the lines into a single string, replacing line breaks with spaces
                            content_str = ' '.join(lines)
                            contents.append("## " + content_str.replace(" , ", ", "))
                            contents.append("")
                    


        if contents:
            joined_contents = '\n'.join(contents)
            return joined_contents
        else:
            print(f"extract_package_commands({class_names}): nothing found")
            return ""
    else:
        print(f"extract_package_commands({class_names}): nothing found")
        return ""

def extract_classnames_between_package_and_installation():
    global soup
    # Find the div element with class "sect1"
    sect1_div = soup.find("div", class_="sect1")

    if sect1_div:
        installation_div = sect1_div.find("div", class_="package")
        configuration_div = sect1_div.find("div", class_="installation")

        if installation_div and configuration_div:
            # Find all siblings of div.installation that are on the same level
            elements_between = []

            for sibling in installation_div.next_siblings:
                if sibling == configuration_div:
                    break

                if sibling.name:
                    elements_between.append(sibling["class"])

            if elements_between:
                classnames = [classname for element_classes in elements_between for classname in element_classes]
                return classnames

def format_array_to_bash_array(input_array):
    formatted_elements = " ".join([f"'{element}'" for element in input_array])
    return f'({formatted_elements})'

if __name__ == "__main__":
    install_groups = []
    # get url from arguments
    parser = argparse.ArgumentParser(description="Fetch data from a URL")
    parser.add_argument("url", help="URL of the webpage to fetch")

    args = parser.parse_args()
    url = args.url

    # Check if pkg is a module
    parsed_url = urlsplit(url)
    fragment = parsed_url.fragment
    
    # Remove URL encoding from the fragment
    if fragment:
        is_module = unquote(fragment)
        install_groups.append(os.path.basename(parsed_url.path.strip('/')).replace(".html", ""))

    #url = "https://www.linuxfromscratch.org/blfs/view/12.0/x/qt5.html"
    #url = "https://www.linuxfromscratch.org/blfs/view/12.0/xsoft/libreoffice.html"
    #url = "https://www.linuxfromscratch.org/blfs/view/12.0/postlfs/mitkrb.html"
    
    # Fetch the webpage and parse it
    fetch_webpage(url)

    print(f'\n\n–––––––––––––––––––––––––––––––––––––\n')
    print(f'Getting content\n')

    if extract_name_version is None:
        extract_name_version = extractPackageNameFull()

    # Get the extract_name
    if extract_name is None:
        extract_name, extracted_version = extractPackageNameVersion(extract_name_version)
    extract_name = extractPackageName()
    # Convert extract_name to lowercase
    extract_name_lower = extract_name.lower()

    # Define the filename
    filename = f"install_{extract_name_version}"

    pkg_link = extract_package_link()
    pkg_name = basename = os.path.basename(pkg_link)
    
    installed_content = extract_package_installed_content()
    # Extract the keys from the dictionary
    installed_content_formatted_keys = [f'"{key.rstrip(":")}"' for key in installed_content.keys()]

    # Create the first string
    installed_content_a = f"installed_content=({' '.join(installed_content_formatted_keys)})"

    # Create the second string by joining the values of the dictionary
    installed_content_b = '\n'.join([f'{key}="{value}"' for key, value in installed_content.items()])

    installation_command = extract_package_commands("div.installation")

    installation_configuration = extract_package_commands("div.configuration >div.sect3")

    classnames_between_package_and_installation = extract_classnames_between_package_and_installation()

    # Open the file for writing
    with open(filename, "w") as script_file:
        # Write the script content to the file
        script_file.write(f'#!/bin/bash\n\n')
        script_file.write(f'############################################\n### {extract_name_version}\n\n')
        script_file.write(f'url="{url}"\n')
        script_file.write(f'name="{extract_name}"\n')
        script_file.write(f'name_version="{extract_name_version}"\n')
        script_file.write(f'link="{pkg_link}"\n')
        script_file.write(f'pkg="{pkg_name}"\n')
        script_file.write(f'pkg_dir="" # this is getting set within the script\n')
        script_file.write(f'md5_sum="{extract_package_md5()}"\n')
        script_file.write(f'additional_links={format_array_to_bash_array(extract_additional_downloads())}\n')
        script_file.write(f'info="{extract_package_info()}"\n')
        script_file.write(f'required={format_array_to_bash_array(extract_package_required_pks())}\n')
        script_file.write(f'recommended={format_array_to_bash_array(extract_package_recommended_pks())}\n')
        script_file.write(f'optional={format_array_to_bash_array(extract_package_optional_pks())}\n')
        script_file.write(f'{installed_content_a}\n')
        script_file.write(f'{installed_content_b}\n')
        if is_module:
            script_file.write(f'install_groups={format_array_to_bash_array(install_groups)}\n')

        script_file.write(f'\n\n')
        script_file.write(f'install_pkg() {{\n')
        if classnames_between_package_and_installation:
            script_file.write(f'#### BEFORE INSTALLATION ####\n\n')
            for b_package in classnames_between_package_and_installation:
                bc_package = "div." + b_package
                script_file.write(f'{extract_package_commands(bc_package)}\n')
            script_file.write(f'#### BEFORE INSTALLATION DONE ####\n\n')
        script_file.write(f'#### INSTALLATION ####\n\n')
        script_file.write(f'# Get tar/zip\n')
        script_file.write(f'[ -f "$pkg" ] || wget -4 $link\n\n')
        script_file.write(f'# Check md5sum\n')
        script_file.write(f'if ! grep -wq "$md5_sum" <<< $(md5sum $pkg); then\n\techo "ERROR: md5sum check failed" && exit\nfi\n\n')
        script_file.write(f'# Get additional links\n')
        # LAST WORKED HERE:
        script_file.write('for li in "${additional_links[@]}"; do wget -4 "$li"; done\n\n')
        if not re.search(fr"^tar .* {re.escape(pkg_name)} .*", installation_command):
            script_file.write(f'# Unpack tar/zip and get pkg_dir variable\n')
            script_file.write(f'rm -rf tmp && mkdir tmp && cd tmp\n')
            if pkg_name.endswith(".zip"):
                script_file.write(f'unzip ../$pkg &&\nif [ "$(ls | wc -w)" = "1" ]; then\n\tpkg_dir="$(ls)"; [ -d ../$pkg_dir ] && rm -rf ../$pkg_dir; cd .. && mv tmp/* . && rm -r tmp\nelse\n\tcd ..\n\t[ -d $name_version ] && rm -rf $name_version\n\tmv tmp $name_version\n\tpkg_dir=$name_version\nfi\n')
            elif pkg_name.endswith((".tar", ".tgz", ".tar.gz", ".tar.bz2", ".tar.xz")):
                script_file.write(f'tar -xvf ../$pkg && if [ "$(ls | wc -w)" = "1" ]; then\n\tpkg_dir="$(ls)";\nelse\n\techo "ERROR: unpacked tar leaves unknown directory: \\"$(ls)\\"" && exit;\nfi &&\ncd ..\n[ -d $pkg_dir ] && rm -rf $pkg_dir\nmv tmp/* . && rm -r tmp\n')
            script_file.write(f'cd $pkg_dir\n')
        script_file.write(f'{installation_command}\n')
        script_file.write(f'\n#### INSTALLATION DONE ####\n\n')
        script_file.write(f'}}\n\n')
        if installation_configuration:
            script_file.write(f'\nconfigure_pkg() {{\n')
            script_file.write(f'\n#### CONFIGURATION ####\n\n')
            script_file.write('echo "## Configuration"\n\n')
            script_file.write(f'{installation_configuration}\n')
            script_file.write(f'\n#### CONFIGURATION DONE ####\n\n')
            script_file.write(f'}}\n\n')
        script_file.write(f'if [[ "$1" == "install" ]]; then install_pkg; else echo -e "you may want to run\\n$0 install"; fi\n')
        if installation_configuration:
            script_file.write(f'if [[ "$1" == "install" ]]; then configure_pkg; fi')
        # script_file.write(f'cd ..; llist_package $(whoami) > pkg.lst 2>&1 & disown')


    print(f'\n\n–––––––––––––––––––––––––––––––––––––')
    print(f'\nSuccessfully created {filename}')
    # Check the syntax of the script without executing it
    try:
        subprocess.run(["bash", "-n", filename], check=True)
        print(f'\n–––––––––––––––––––––––––––––––––––––')
        print("\nBash script syntax is valid.")
    except subprocess.CalledProcessError as e:
        print(f'\n–––––––––––––––––––––––––––––––––––––')
        print("\n!!! Error in Bash script syntax !!!")
        if e.stderr is not None:
            print(e.stderr)
    print(f'\n–––––––––––––––––––––––––––––––––––––')
    print(f'\nFile path: {filename}')