def CreateDEB(self, bundle_id, recorded_version): """ Creates a DEB from information stored in the "temp" folder. String bundle_id: The bundle id of the package to compress. """ # TODO: Find a Python-based method to safely delete all DS_Store files. call(["find", ".", "-name", ".DS_Store", "-delete"], cwd=self.root + "temp/" + bundle_id) # Remove .DS_Store. Kinda finicky. for file_name in os.listdir(self.root + "temp/" + bundle_id): if file_name.endswith(".deb"): # Check if the DEB is a newer version deb = Dpkg(self.root + "temp/" + bundle_id + "/" + file_name) if Dpkg.compare_versions(recorded_version, deb.version) == -1: # Update package stuff package_name = PackageLister.BundleIdToDirName( self, bundle_id) with open( self.root + "Packages/" + package_name + "/silica_data/index.json", "r") as content_file: update_json = json.load(content_file) update_json['version'] = deb.version changelog_entry = input( "The DEB provided for \"" + update_json['name'] + "\" has a new version available (" + recorded_version + " -> " + deb.version + "). What changed in this version?\n(Add multiple lines" + " by using a newline character [\\n] and use valid Markdown syntax.): " ) try: update_json['changelog'].append({ "version": deb.version, "changes": changelog_entry }) except Exception: update_json['changelog'] = { "version": deb.version, "changes": changelog_entry } return_str = json.dumps(update_json) print("Updating package index.json...") PackageLister.CreateFile( self, "Packages/" + package_name + "/silica_data/index.json", return_str) pass DpkgPy.extract( self, self.root + "temp/" + bundle_id + "/" + file_name, self.root + "temp/" + bundle_id) os.remove(self.root + "temp/" + bundle_id + "/" + file_name) os.remove(self.root + "temp/" + bundle_id + "/control") else: # TODO: Update DpkgPy to generate DEB files without dependencies (for improved win32 support) call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB
def NativeFeaturedCarousel(self, tweak_release): """ Generate a sileo-featured.json file for featured packages. Object carousel_entry_list: A "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) subfolder = PackageLister.FullPathCname(self, repo_settings) banners = [] for package in tweak_release: try: if package['featured'].lower() == "true": ar_el = { "package": package['bundle_id'], "title": package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) except Exception: pass if len(banners) == 0: try: featured_int = random.randint(0, (len(tweak_release) - 1)) except Exception: PackageLister.ErrorReporter( self, "Configuration Error!", "You have no packages added to this repo. " "Make sure a folder is created at \"" + self.version + "/Packages\" that contains folders with " "package data inside of them and run Silica again.") featured_package = tweak_release[featured_int] ar_el = { "package": featured_package['bundle_id'], "title": featured_package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + featured_package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) featured_json = { "class": "FeaturedBannersView", "itemSize": "{263, 148}", "itemCornerRadius": 8, "banners": banners } return json.dumps(featured_json, separators=(',', ':'))
def RenderIndexHTML(self): """ Renders the home page (index.html). """ repo_settings = PackageLister.GetRepoSettings(self) with open(self.root + "Styles/index.mustache", "r") as content_file: index = content_file.read() replacements = DepictionGenerator.RenderDataHTML(self) replacements['tint_color'] = repo_settings['tint'] replacements['footer'] = DepictionGenerator.RenderFooter(self) replacements['tweak_release'] = PackageLister.GetTweakRelease(self) return pystache.render(index, replacements)
def NativeFeaturedCarousel(self, tweak_release): """ Generate a sileo-featured.json file for featured packages. Object carousel_entry_list: A "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) subfolder = PackageLister.FullPathCname(self, repo_settings) banners = [] for package in tweak_release: try: if package['featured'].lower() == "true": ar_el = { "package": package['bundle_id'], "title": package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) except Exception: pass if len(banners) == 0: featured_int = random.randint(0, (len(tweak_release) - 1)) featured_package = tweak_release[featured_int] ar_el = { "package": featured_package['bundle_id'], "title": featured_package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + featured_package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) featured_json = { "class": "FeaturedBannersView", "itemSize": "{263, 148}", "itemCornerRadius": 8, "banners": banners } return json.dumps(featured_json)
def CarouselEntryList(self, tweak_release): """ Generate a carousel of featured packages on the repo. Object tweak_release: A "tweak release" object. """ list_el = "" for package in tweak_release: try: if package['featured'].lower() == "true": list_el += DepictionGenerator.CarouselEntry( self, package['name'], "assets/" + package['bundle_id'] + "/banner.png", package['bundle_id']) except Exception: pass if list_el == "": try: featured_int = random.randint(0, (len(tweak_release) - 1)) except Exception: PackageLister.ErrorReporter( self, "Configuration Error!", "You have no packages added to this repo." " Make sure a folder is created at \"" + self.version + "/Packages\" that contains folders with package data inside of them and run Silica again." ) featured_package = tweak_release[featured_int] list_el += DepictionGenerator.CarouselEntry( self, featured_package['name'], "assets/" + featured_package['bundle_id'] + "/banner.png", featured_package['bundle_id']) return list_el
def SignRelease(self): """ Signs Release to create Release.gpg. Also adds hash for Packages.bz2 in Release. """ with open(self.root + "docs/Packages.bz2", "rb") as content_file,\ open(self.root + "docs/Packages.xz", "rb") as content_file_xz: bzip_raw = content_file.read() bzip_sha256_hash = hashlib.sha256(bzip_raw).hexdigest() bzip_size = os.path.getsize(self.root + "docs/Packages.bz2") xz_raw = content_file_xz.read() xz_sha256_hash = hashlib.sha256(xz_raw).hexdigest() xz_size = os.path.getsize(self.root + "docs/Packages.xz") with open(self.root + "docs/Release", "a") as text_file: text_file.write("\nSHA256:\n " + str(bzip_sha256_hash) + " " + str(bzip_size) + " Packages.bz2" "\n " + str(xz_sha256_hash) + " " + str(xz_size) + " Packages.xz\n") repo_settings = PackageLister.GetRepoSettings(self) try: if repo_settings['enable_gpg'].lower() == "true": print("Signing repository with GPG...") key = "Silica MobileAPT Repository" # Most of the time, this is acceptable. call([ "gpg", "-abs", "-u", key, "-o", "Release.gpg", "Release" ], cwd=self.root + "docs/") print("Generated Release.gpg!") except Exception: pass
def RenderDataBasic(self): """ Gets the value of basic repo data to pass to Pystache. """ repo_settings = PackageLister.GetRepoSettings(self) with open(self.root + "Styles/settings.json", "r") as content_file: data = json.load(content_file) date = datetime.datetime.now().strftime("%Y-%m-%d") subfolder = PackageLister.FullPathCname(self, repo_settings) return { "silica_version": self.version, "silica_compile_date": date, "repo_name": data['name'], "repo_url": data['cname'] + subfolder, "repo_desc": data['description'], "repo_tint": data['tint'] }
def RenderDataHTML(self): data = DepictionGenerator.RenderDataBasic(self) tweak_release = PackageLister.GetTweakRelease(self) data['repo_packages'] = DepictionGenerator.PackageEntryList(self, tweak_release) data['repo_carousel'] = DepictionGenerator.CarouselEntryList(self, tweak_release) return data
def RenderPackageHTML(self, tweak_data): """ Renders a package's depiction. Object tweak_data: A single index of a "tweak release" object. """ with open(self.root + "Styles/tweak.mustache", "r") as content_file: index = content_file.read() replacements = DepictionGenerator.RenderDataHTML(self) replacements['tweak_name'] = tweak_data['name'] replacements['tweak_developer'] = tweak_data['developer']['name'] replacements['tweak_compatibility'] = "iOS " + tweak_data[ 'works_min'] + " to " + tweak_data['works_max'] replacements['tweak_version'] = tweak_data['version'] replacements['tweak_section'] = tweak_data['section'] replacements['tweak_bundle_id'] = tweak_data['bundle_id'] replacements['works_min'] = tweak_data['works_min'] replacements['works_max'] = tweak_data['works_max'] replacements[ 'tweak_carousel'] = DepictionGenerator.ScreenshotCarousel( self, tweak_data) replacements[ 'tweak_changelog'] = DepictionGenerator.RenderChangelogHTML( self, tweak_data) replacements['tweak_tagline'] = tweak_data['tagline'] replacements['footer'] = DepictionGenerator.RenderFooter(self) try: if tweak_data['source'] != "": replacements['source'] = tweak_data['source'] except Exception: pass try: replacements['tint_color'] = tweak_data['tint'] except Exception: try: repo_settings = PackageLister.GetRepoSettings(self) replacements['tint_color'] = repo_settings['tint'] except Exception: replacements['tint_color'] = "#2cb1be" try: with open( self.root + "repo/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: raw_md = md_file.read() desc_md = mistune.markdown(raw_md) replacements['tweak_description'] = desc_md except Exception: replacements['tweak_description'] = tweak_data['tagline'] # tweak_carousel return pystache.render(index, replacements)
def RenderFooter(self): """ Renders the footer. """ repo_settings = PackageLister.GetRepoSettings(self) data = DepictionGenerator.RenderDataHTML(self) try: footer = pystache.render(repo_settings['footer'], data) except Exception: footer = pystache.render("Silica {{silica_version}} – Updated {{silica_compile_date}}", data) return footer
def ScreenshotCarousel(self, tweak_data): """ Generates a screenshot div. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) screenshot_div = "<div class=\"scroll_view\">" image_list = self.PackageLister.GetScreenshots(tweak_data) if (len(image_list) > 0): for image in image_list: screenshot_div += '''<img class="img_card" src="../../assets/{1}/screenshot/{2}">'''.format( repo_settings['cname'], tweak_data['bundle_id'], image) screenshot_div += "</div>" else: screenshot_div = "" return screenshot_div
########### # Step 9: Make API endpoints ########### PackageLister.CreateFile("repo/api/tweak_release.json", json.dumps(tweak_release)) PackageLister.CreateFile("repo/api/repo_settings.json", json.dumps(repo_settings)) PackageLister.CreateFile("repo/api/about.json", json.dumps(DepictionGenerator.SilicaAbout())) ########### # Step 10: Push to GitHub ########### shutil.rmtree(root + "temp/") # Clean-up the now-unneeded temp folder. try: if repo_settings['automatic_git'].lower() == "true": DebianPackager.PushToGit( ) # Push the repo to GitHub automatically. except Exception: pass if __name__ == '__main__': DepictionGenerator = DepictionGenerator(version) PackageLister = PackageLister(version) DebianPackager = DebianPackager(version) main()
def main(): print("Silica Compiler {0}".format(version)) ########### # Step 0: Clean up "repo" and "temp" folder ########### root = os.path.dirname(os.path.abspath(__file__)) + "/" try: shutil.rmtree(root + "repo/") except Exception: pass try: shutil.rmtree(root + "temp/") except Exception: pass ########### # Step 1: Generate folders, files, and variables needed (including temp) ########### PackageLister.CreateFolder("repo") PackageLister.CreateFolder("repo/web") PackageLister.CreateFolder("repo/depiction") PackageLister.CreateFolder("repo/depiction/web") PackageLister.CreateFolder("repo/depiction/native") PackageLister.CreateFolder("repo/depiction/native/help") PackageLister.CreateFolder("repo/pkg") PackageLister.CreateFolder("repo/assets") PackageLister.CreateFolder("repo/api") # Make sure all index.json files are generated (if using DEBs) DebianPackager.CheckForSilicaData() tweak_release = PackageLister.GetTweakRelease() repo_settings = PackageLister.GetRepoSettings() # Create folder for each tweak for tweak in tweak_release: PackageLister.CreateFolder("repo/assets/" + tweak['bundle_id']) ########### # Step 2: Copy all images ########### for package_name in PackageLister.ListDirNames(): package_bundle_id = PackageLister.DirNameToBundleID(package_name) try: shutil.copy( root + "Packages/" + package_name + "/silica_data/icon.png", root + "repo/assets/" + package_bundle_id + "/icon.png") except Exception: category = PackageLister.ResolveCategory(tweak_release, package_bundle_id) category = re.sub(r'\([^)]*\)', '', category).strip() try: shutil.copy( root + "Styles/Generic/Icon/" + category + ".png", root + "repo/assets/" + package_bundle_id + "/icon.png") except Exception: try: shutil.copy( root + "Styles/Generic/Icon/Generic.png", root + "repo/assets/" + package_bundle_id + "/icon.png") except Exception: PackageLister.ErrorReporter( "Configuration Error!", "You are missing a file at " + root + "Styles/Generic/Icon/Generic.png. Please place an icon here to be the repo's default." ) try: shutil.copy( root + "Packages/" + package_name + "/silica_data/banner.png", root + "repo/assets/" + package_bundle_id + "/banner.png") except Exception: category = PackageLister.ResolveCategory(tweak_release, package_bundle_id) category = re.sub(r'\([^)]*\)', '', category).strip() try: shutil.copy( root + "Styles/Generic/Banner/" + category + ".png", root + "repo/assets/" + package_bundle_id + "/banner.png") except Exception: try: shutil.copy( root + "Styles/Generic/Banner/Generic.png", root + "repo/assets/" + package_bundle_id + "/banner.png") except Exception: PackageLister.ErrorReporter( "Configuration Error!", "You are missing a file at " + root + "Styles/Generic/Banner/Generic.png. Please place a banner here to be the repo's default." ) try: shutil.copy( root + "Packages/" + package_name + "/silica_data/description.md", root + "repo/assets/" + package_bundle_id + "/description.md") except Exception: pass try: shutil.copytree( root + "Packages/" + package_name + "/silica_data/screenshots", root + "repo/assets/" + package_bundle_id + "/screenshot") except Exception: pass try: shutil.copy(root + "Styles/icon.png", root + "repo/CydiaIcon.png") except Exception: PackageLister.ErrorReporter( "Configuration Error!", "You are missing a file at " + root + "Styles/icon.png. " "Please add a PNG here to act as the repo's icon.") ########### # Step 3: Generate HTML depictions and copy stylesheet ########### # Copy CSS and JS over shutil.copy(root + "Styles/index.css", root + "repo/web/index.css") shutil.copy(root + "Styles/index.js", root + "repo/web/index.js") # Generate index.html index_html = DepictionGenerator.RenderIndexHTML() PackageLister.CreateFile("repo/index.html", index_html) PackageLister.CreateFile("repo/404.html", index_html) # Generate per-tweak depictions for tweak_data in tweak_release: tweak_html = DepictionGenerator.RenderPackageHTML(tweak_data) PackageLister.CreateFile( "repo/depiction/web/" + tweak_data['bundle_id'] + ".html", tweak_html) PackageLister.CreateFile("repo/CNAME", repo_settings['cname']) ########### # Step 4: Generate Sileo depictions and featured JSON ########### # Generate sileo-featured.json carousel_obj = DepictionGenerator.NativeFeaturedCarousel(tweak_release) PackageLister.CreateFile("repo/sileo-featured.json", carousel_obj) # Generate per-tweak depictions for tweak_data in tweak_release: tweak_json = DepictionGenerator.RenderPackageNative(tweak_data) PackageLister.CreateFile( "repo/depiction/native/" + tweak_data['bundle_id'] + ".json", tweak_json) help_depiction = DepictionGenerator.RenderNativeHelp(tweak_data) PackageLister.CreateFile( "repo/depiction/native/help/" + tweak_data['bundle_id'] + ".json", help_depiction) ########### # Step 5: Generate Release file from settings.json. ########### release_file = DebianPackager.CompileRelease(repo_settings) PackageLister.CreateFile("repo/Release", release_file) ########### # Step 6: Copy packages to temp ########### # You should remove .DS_Store files AFTER copying to temp. PackageLister.CreateFolder("temp") for package_name in PackageLister.ListDirNames(): bundle_id = PackageLister.DirNameToBundleID(package_name) try: shutil.copytree(root + "Packages/" + package_name, root + "temp/" + bundle_id) shutil.rmtree(root + "temp/" + bundle_id + "/silica_data") except Exception: try: shutil.rmtree(root + "temp/" + bundle_id + "/silica_data") except Exception: pass script_check = Path(root + "Packages/" + package_name + "/silica_data/scripts/") if script_check.is_dir(): shutil.copytree( root + "Packages/" + package_name + "/silica_data/scripts", root + "temp/" + bundle_id + "/DEBIAN") else: PackageLister.CreateFolder("temp/" + bundle_id + "/DEBIAN") ########### # Step 7: Generate CONTROL and DEB files and move them to repo/ ########### for tweak_data in tweak_release: control_file = DebianPackager.CompileControl(tweak_data, repo_settings) #input(); PackageLister.CreateFile( "temp/" + tweak_data['bundle_id'] + "/DEBIAN/control", control_file) DebianPackager.CreateDEB(tweak_data['bundle_id'], tweak_data['version']) shutil.copy(root + "temp/" + tweak_data['bundle_id'] + ".deb", root + "repo/pkg/" + tweak_data['bundle_id'] + ".deb") ########### # Step 8: Generate Package file and hash/sign the Release file. ########### DebianPackager.CompilePackages() DebianPackager.SignRelease() ########### # Step 9: Make API endpoints ########### PackageLister.CreateFile("repo/api/tweak_release.json", json.dumps(tweak_release)) PackageLister.CreateFile("repo/api/repo_settings.json", json.dumps(repo_settings)) PackageLister.CreateFile("repo/api/about.json", json.dumps(DepictionGenerator.SilicaAbout())) ########### # Step 10: Push to GitHub ########### shutil.rmtree(root + "temp/") # Clean-up the now-unneeded temp folder. try: if repo_settings['automatic_git'].lower() == "true": DebianPackager.PushToGit( ) # Push the repo to GitHub automatically. except Exception: pass
def CheckForSilicaData(self): """ Ensures that a silica_data file exists and if it doesn't, try to create one with as much data as we have. If there is a DEB file, it will take data from its CONTROL file. It will also auto-update the version number. If there is no DEB file, it will use the name of the folder, version 1.0.0, try to guess some dependencies, and add some placeholder data. :return: """ for folder in os.listdir(self.root + "Packages"): if folder.lower() != ".ds_store": if not os.path.isdir(self.root + "Packages/" + folder + "/silica_data"): print("It seems like the package \"" + folder + "\" is not configured. Let's set it up!") is_deb = False deb_path = "" try: for file_name in os.listdir(self.root + "Packages/" + folder): if file_name.endswith(".deb"): is_deb = True deb_path = self.root + "Packages/" + folder + "/" + file_name except Exception: PackageLister.ErrorReporter(self, "Configuration Error!", "Please put your .deb file inside of " "its own folder. The \"Packages\" directory should be made of multiple folders that each " "contain data for a single package.\n Please fix this issue and try again.") # This will be the default scaffolding for our package. Eventually I'll neuter it to only be the # essential elements; it's also kinda a reference to me. output = { "bundle_id": "co.shuga.silica.unknown", "name": "Unknown Package", "version": "1.0.0", "tagline": "An unknown package.", "homepage": "https://shuga.co/", "developer": { "name": "Unknown", "email": "*****@*****.**" }, "maintainer": { "name": "Unknown", "email": "*****@*****.**" }, "section": "Themes", "works_min": "8.0", "works_max": "13.0", "featured": "false" } if is_deb: print("Extracting data from DEB...") deb = Dpkg(deb_path) output['name'] = deb.headers['Name'] output['bundle_id'] = deb.headers['Package'] try: output['tagline'] = deb.headers['Description'] except Exception: output['tagline'] = input("What is a brief description of the package? ") try: output['homepage'] = deb.headers['Homepage'] except Exception: pass try: remove_email_regex = re.compile('<.*?>') output['developer']['name'] = remove_email_regex.sub("", deb.headers['Author']) except Exception: output['developer']['name'] = input("Who originally made this package? This may be" " your name. ") output['developer']['email'] = input("What is the original author's email address? ") try: remove_email_regex = re.compile('<.*?>') output['maintainer']['name'] = remove_email_regex.sub("", deb.headers['Maintainer']) except Exception: output['maintainer']['name'] = input("Who maintains this package now?" " This is likely your name. ") output['maintainer']['email'] = input("What is the maintainer's email address? ") try: output['sponsor']['name'] = remove_email_regex.sub("", deb.headers['Sponsor']) except Exception: pass try: output['dependencies'] = deb.headers['Depends'] except Exception: pass try: output['section'] = deb.headers['Section'] except Exception: pass try: output['version'] = deb.headers['Version'] except Exception: output['version'] = "1.0.0" try: output['conflicts'] = deb.headers['Conflicts'] except Exception: pass try: output['replaces'] = deb.headers['Replaces'] except Exception: pass try: output['provides'] = deb.headers['Provides'] except Exception: pass try: output['build_depends'] = deb.headers['Build-Depends'] except Exception: pass try: output['recommends'] = deb.headers['Recommends'] except Exception: pass try: output['suggests'] = deb.headers['Suggests'] except Exception: pass try: output['enhances'] = deb.headers['Enhances'] except Exception: pass try: output['breaks'] = deb.headers['Breaks'] except Exception: pass try: output['tags'] = deb.headers['Tag'] except Exception: pass try: output['suggests'] = deb.headers['Suggests'] except Exception: pass # These still need data. output['works_min'] = input("What is the lowest iOS version the package works on? ") output['works_max'] = input("What is the highest iOS version the package works on? ") output['featured'] = input("Should this package be featured on your repo? (true/false) ") set_tint = input("What would you like this package's tint color to be? To keep it at" " the default, leave this blank: ") if set_tint != "": output['tint'] = set_tint print("All done! Please look over the generated \"index.json\" file and consider populating the" " \"silica_data\" folder with a description, screenshots, and an icon.") # Extract Control file and scripts from DEB DpkgPy.control_extract(self, deb_path, self.root + "Packages/" + folder + "/silica_data/scripts/") # Remove the Control; it's not needed. os.remove(self.root + "Packages/" + folder + "/silica_data/scripts/Control") if not os.listdir(self.root + "Packages/" + folder + "/silica_data/scripts/"): os.rmdir(self.root + "Packages/" + folder + "/silica_data/scripts/") else: print("Estimating dependencies...") # Use the filesystem to see if Zeppelin, Anemone, LockGlyph, XenHTML, and similar. # If one of these are found, set it as a dependency. # If multiple of these are found, use a hierarchy system, with Anemone as the highest priority, # for determining the category. output['dependencies'] = "" output['section'] = "Themes" if os.path.isdir(self.root + "Packages/" + folder + "/Library/Zeppelin"): output['section'] = "Themes (Zeppelin)" output['dependencies'] += "com.alexzielenski.zeppelin, " if os.path.isdir(self.root + "Packages/" + folder + "/Library/Application Support/LockGlyph"): output['section'] = "Themes (LockGlyph)" output['dependencies'] += "com.evilgoldfish.lockglypgh, " if os.path.isdir(self.root + "Packages/" + folder + "/var/mobile/Library/iWidgets"): output['section'] = "Widgets" output['dependencies'] += "com.matchstic.xenhtml, " if os.path.isdir(self.root + "Packages/" + folder + "/Library/Wallpaper"): output['section'] = "Wallpapers" if os.path.isdir(self.root + "Packages/" + folder + "/Library/Themes"): output['section'] = "Themes" output['dependencies'] += "com.anemonetheming.anemone, " if output['dependencies'] != "": output['dependencies'] = output['dependencies'][:-2] repo_settings = PackageLister.GetRepoSettings(self) # Ask for name output['name'] = input("What should we name this package? ") # Automatically generate a bundle ID from the package name. domain_breakup = repo_settings['cname'].split(".")[::-1] only_alpha_regex = re.compile('[^a-zA-Z]') machine_safe_name = only_alpha_regex.sub("", output['name']).lower() output['bundle_id'] = ".".join(str(x) for x in domain_breakup) + "." + machine_safe_name output['tagline'] = input("What is a brief description of the package? ") output['homepage'] = "https://" + repo_settings['cname'] # I could potentially default this to what is in settings.json but attribution may be an issue. output['developer']['name'] = input("Who made this package? This is likely your name. ") output['developer']['email'] = input("What is the author's email address? ") output['works_min'] = input("What is the lowest iOS version the package works on? ") output['works_max'] = input("What is the highest iOS version the package works on? ") output['featured'] = input("Should this package be featured on your repo? (true/false) ") PackageLister.CreateFolder(self, "Packages/" + folder + "/silica_data/") PackageLister.CreateFile(self, "Packages/" + folder + "/silica_data/index.json", json.dumps(output))
def CompileControl(self, tweak_data, repo_settings): """ Compiles a CONTROL file from a tweak_data object Object tweak_data: A single index of a "tweak release" object. Object repo_settings: An object of repo settings. """ subfolder = PackageLister.FullPathCname(self, repo_settings) control_file = "Architecture: iphoneos-arm\n" # Mandatory properties include name, bundle id, and version. control_file += "Package: " + tweak_data['bundle_id'] + "\n" control_file += "Name: " + tweak_data['name'] + "\n" control_file += "Version: " + tweak_data['version'] + "\n" # Known properties control_file += "Depiction: https://" + repo_settings['cname'] + subfolder + "/depiction/web/" + tweak_data[ 'bundle_id'] \ + ".html\n" control_file += "SileoDepiction: https://" + repo_settings['cname'] + subfolder + "/depiction/native/" \ + tweak_data['bundle_id'] + ".json\n" control_file += "ModernDepiction: https://" + repo_settings['cname'] + subfolder + "/depiction/native/" \ + tweak_data['bundle_id'] + ".json\n" control_file += "Icon: https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data[ 'bundle_id'] + "/icon.png\n" # Optional properties try: if tweak_data['tagline']: # APT note: Multi-line descriptions are in the spec, but must be indicated with a leading space. control_file += "Description: " + tweak_data['tagline'].replace("\n\n", "\n .\n ").replace("\n", "\n ") + "\n" except Exception: control_file += "Description: An awesome package!\n" try: if tweak_data['homepage']: control_file += "Homepage: " + tweak_data['homepage'] + "\n" except Exception: pass try: if tweak_data['section']: control_file += "Section: " + tweak_data['section'] + "\n" except Exception: control_file += "Section: Unknown\n" try: if tweak_data['pre_dependencies']: control_file += "Pre-Depends: " + tweak_data['pre_dependencies'] + "\n" except Exception: pass try: if tweak_data['dependencies']: control_file += "Depends: firmware (>=" + tweak_data['works_min'] + "), " + tweak_data[ 'dependencies'] + "\n" except Exception: control_file += "Depends: firmware (>=" + tweak_data['works_min'] + ")\n" try: if tweak_data['conflicts']: control_file += "Conflicts: " + tweak_data['conflicts'] + "\n" except Exception: pass try: if tweak_data['replaces']: control_file += "Replaces: " + tweak_data['replaces'] + "\n" except Exception: pass try: if tweak_data['provides']: control_file += "Provides: " + tweak_data['provides'] + "\n" except Exception: pass try: if tweak_data['build_depends']: control_file += "Build-Depends: " + tweak_data['build_depends'] + "\n" except Exception: pass try: if tweak_data['recommends']: control_file += "Recommends: " + tweak_data['recommends'] + "\n" except Exception: pass try: if tweak_data['suggests']: control_file += "Suggests: " + tweak_data['suggests'] + "\n" except Exception: pass try: if tweak_data['enhances']: control_file += "Enhances: " + tweak_data['enhances'] + "\n" except Exception: pass try: if tweak_data['breaks']: control_file += "Breaks: " + tweak_data['breaks'] + "\n" except Exception: pass try: if tweak_data['tags']: control_file += "Tags: compatible_min::ios" + tweak_data['works_min'] + ", compatible_max::ios" + tweak_data['works_max'] + ", " + tweak_data['tags'] + "\n" except Exception: control_file += "Tags: compatible_min::ios" + tweak_data['works_min'] + ", compatible_max::ios" + tweak_data['works_max'] + "\n" try: if tweak_data['developer']: try: if tweak_data['developer']['email']: control_file += "Author: " + tweak_data['developer']['name'] + " <" + tweak_data['developer'][ 'email'] + ">\n" except Exception: control_file += "Author: " + tweak_data['developer']['name'] + "\n" except Exception: control_file += "Author: Unknown\n" try: if tweak_data['maintainer']['email']: control_file += "Maintainer: " + tweak_data['maintainer']['name'] + " <" \ + tweak_data['maintainer']['email'] + ">\n" except Exception: try: control_file += "Maintainer: " + tweak_data['maintainer']['name'] + "\n" except Exception: try: if tweak_data['developer']['email']: control_file += "Maintainer: " + tweak_data['developer']['name'] + " <" \ + tweak_data['developer']['email'] + ">\n" except Exception: try: control_file += "Maintainer: " + tweak_data['developer']['name'] + "\n" except Exception: control_file += "Maintainer: Unknown\n" try: if tweak_data['sponsor']: try: if tweak_data['sponsor']['email']: control_file += "Sponsor: " + tweak_data['sponsor']['name'] + " <" + tweak_data['sponsor'][ 'email'] + ">\n" except Exception: control_file += "Sponsor: " + tweak_data['sponsor']['name'] + "\n" except Exception: pass # other_control try: if tweak_data['other_control']: for line in tweak_data['other_control']: control_file += line + "\n" except Exception: pass return control_file
def __init__(self, version): super(DebianPackager, self).__init__() self.version = version self.root = os.path.dirname(os.path.abspath(__file__)) + "/../" self.PackageLister = PackageLister(self.version)
def CreateDEB(self, bundle_id, recorded_version): """ Creates a DEB from information stored in the "temp" folder. String bundle_id: The bundle id of the package to compress. String recorded_version: Object tweak_release: A "tweak release" object. """ # TODO: Find a Python-based method to safely delete all DS_Store files. call(["find", ".", "-name", ".DS_Store", "-delete"], cwd=self.root + "temp/" + bundle_id) # Remove .DS_Store. Kinda finicky. for file_name in os.listdir(self.root + "temp/" + bundle_id): if file_name.endswith(".deb"): # Check if the DEB is a newer version deb = Dpkg(self.root + "temp/" + bundle_id + "/" + file_name) if Dpkg.compare_versions(recorded_version, deb.version) == -1: # Update package stuff package_name = PackageLister.BundleIdToDirName(self, bundle_id) with open(self.root + "Packages/" + package_name + "/silica_data/index.json", "r") as content_file: update_json = json.load(content_file) update_json['version'] = deb.version changelog_entry = input("The DEB provided for \"" + update_json['name'] + "\" has a new version available (" + recorded_version + " -> " + deb.version + "). What changed in this version?\n(Add multiple lines" + " by using newline characters [\\n\\n] and use valid Markdown syntax): " ) try: update_json['changelog'].append( { "version": deb.version, "changes": changelog_entry } ) except Exception: # Make it a list! update_json['changelog'] = [] update_json['changelog'].append( { "version": deb.version, "changes": changelog_entry } ) # A small note: We already created the variables that contain the changelogs and, to # make matters worse, all the web assets. The only way to mitigate this is to re-create the # tweak_release variable again, which wrecks a lot of things (ie runtime). # A Silica rewrite is required to properly fix this bug. print("\nA small warning about adding changelogs mid-run:\n") print("Due to some less-than-ideal design decisions with Silica, for the changelog to show") print("up, you're going to have to run Silica again. Yes, I know this is annoying, and a proper") print("solution is in the works, but the under-the-hood changes that'll be needed to fix") print("it properly would require a rewrite [see issue #22].\n") print("I'm deeply sorry about this.\n - Shuga.\n") # Get human-readable folder name folder = PackageLister.BundleIdToDirName(self, bundle_id) deb_path = self.root + "Packages/" + folder + "/" + file_name # Extract Control file and scripts from DEB DpkgPy.control_extract(self, deb_path, self.root + "Packages/" + folder + "/silica_data/scripts/") # Remove the Control; it's not needed. os.remove(self.root + "Packages/" + folder + "/silica_data/scripts/Control") if not os.listdir(self.root + "Packages/" + folder + "/silica_data/scripts/"): os.rmdir(self.root + "Packages/" + folder + "/silica_data/scripts/") return_str = json.dumps(update_json) print("Updating package index.json...") PackageLister.CreateFile(self, "Packages/" + package_name + "/silica_data/index.json", return_str) pass DpkgPy.extract(self, self.root + "temp/" + bundle_id + "/" + file_name, self.root + "temp/" + bundle_id) try: os.remove(self.root + "temp/" + bundle_id + "/" + file_name) except: pass try: os.remove(self.root + "temp/" + bundle_id + "/control") except: pass else: # TODO: Update DpkgPy to generate DEB files without dependencies (for improved win32 support) # If the version is consistent, then assume the package is unchanged. Don't regenerate it. try: # Check for a DEB that already exists. docs_deb = Dpkg(self.root + "docs/pkg/" + bundle_id + ".deb") if docs_deb.version == recorded_version: shutil.copy(self.root + "docs/pkg/" + bundle_id + ".deb", self.root + "temp/" + bundle_id + ".deb") call_result = 0; else: # Sneaky swap. call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB except: # Create the DEB again. call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB if call_result != 0: # Did we run within WSL? if "Microsoft" in platform.release(): PackageLister.ErrorReporter(self, "Platform Error!", "dpkg-deb failed to run. " "This is likely due to improper configuration of WSL. Please check the Silcia README for " "how to set up WSL for dpkg-deb.") else: PackageLister.ErrorReporter(self, "DPKG Error!", "dpkg-deb failed to run. " "This could be due to a faulty system configuration.")
def RenderPackageHTML(self, tweak_data): """ Renders a package's depiction. Object tweak_data: A single index of a "tweak release" object. """ with open(self.root + "Styles/tweak.mustache", "r") as content_file: index = content_file.read() replacements = DepictionGenerator.RenderDataHTML(self) try: replacements['tweak_name'] = tweak_data['name'] except: PackageLister.ErrorReporter( self, "Configuration Error!", "You are missing a package " "name in its index.json. Make sure this and other required properties are set." ) try: replacements['tweak_developer'] = tweak_data['developer'][ 'name'] replacements['tweak_compatibility'] = "iOS " + tweak_data[ 'works_min'] + " to " + tweak_data['works_max'] replacements['tweak_version'] = tweak_data['version'] replacements['tweak_section'] = tweak_data['section'] replacements['tweak_bundle_id'] = tweak_data['bundle_id'] replacements['works_min'] = tweak_data['works_min'] replacements['works_max'] = tweak_data['works_max'] replacements['tweak_tagline'] = tweak_data['tagline'] except: PackageLister.ErrorReporter( self, "Configuration Error!", "You are missing an essential " "property in " + tweak_data['name'] + "'s index.json. Make sure developer, version, section, " "bundle id, and tagline are set properly.") replacements[ 'tweak_carousel'] = DepictionGenerator.ScreenshotCarousel( self, tweak_data) replacements[ 'tweak_changelog'] = DepictionGenerator.RenderChangelogHTML( self, tweak_data) replacements['footer'] = DepictionGenerator.RenderFooter(self) try: if tweak_data['source'] != "": replacements['source'] = tweak_data['source'] except Exception: pass try: replacements['tint_color'] = tweak_data['tint'] except Exception: try: repo_settings = PackageLister.GetRepoSettings(self) replacements['tint_color'] = repo_settings['tint'] except Exception: replacements['tint_color'] = "#2cb1be" try: with open( self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: raw_md = md_file.read() desc_md = mistune.markdown(raw_md) replacements['tweak_description'] = desc_md except Exception: replacements['tweak_description'] = tweak_data['tagline'] # tweak_carousel return pystache.render(index, replacements)
def RenderNativeHelp(self, tweak_data): """ Generates a help view for Sileo users. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) try: tint = tweak_data['tint'] except Exception: try: tint = repo_settings['tint'] except Exception: tint = "#2cb1be" view = [] try: if tweak_data['developer']['email']: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " + tweak_data['developer']['name'] + ", the developer, via e-mail." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Developer", "action": "mailto:" + tweak_data['developer']['email'], "openExternal": "true", "tintColor": tint }) except Exception: try: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " + tweak_data['developer']['name'] + ", who is the developer. Sadly, we don't know their email." }) except Exception: view.append({ "class": "DepictionMarkdownView", "markdown": "The developer of the package \"" + tweak_data['name'] + "\" is not known. Try contacting the repo owner for more information." }) try: if tweak_data['social']: view.append({ "class": "DepictionMarkdownView", "markdown": "You can also contact " + tweak_data['developer']['name'] + " using the following" + " sites:" }) for entry in tweak_data['social']: view.append({ "class": "DepictionTableButtonView", "title": entry['name'], "action": entry['url'], "openExternal": "true", "tintColor": tint }) except Exception: pass try: if tweak_data['maintainer']['email']: view.append({ "class": "DepictionMarkdownView", "markdown": tweak_data['maintainer']['name'] + " is the maintainer of the package \"" + tweak_data['name'] + "\". Please contact them via email for any questions on this" " version of the package." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Maintainer", "action": "mailto:" + tweak_data['maintainer']['email'], "openExternal": "true", "tintColor": tint }) except Exception: try: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you should contact " + tweak_data['maintainer']['name'] + ", who is the package's current maintainer. Sadly, we don't know their email." }) except Exception: pass view.append({ "class": "DepictionMarkdownView", "markdown": "If you found a mistake in the depiction or cannot download the package, you can reach out" + " to the maintainer of the \"" + repo_settings['name'] + "\" repo, " + repo_settings['maintainer']['name'] + "." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Repo Maintainer", "action": "mailto:" + repo_settings['maintainer']['email'], "openExternal": "true", "tintColor": tint }) try: if repo_settings['social']: view.append({ "class": "DepictionMarkdownView", "markdown": "You can also contact the repo owner via the following" + " sites:" }) for entry in repo_settings['social']: view.append({ "class": "DepictionTableButtonView", "title": entry['name'], "action": entry['url'], "openExternal": "true", "tintColor": tint }) except Exception: pass return json.dumps( { "class": "DepictionStackView", "tintColor": tint, "title": "Contact Support", "views": view }, separators=(',', ':'))
class DepictionGenerator: """ DepictionGenerator deals with the rendering and generating of depictions. """ def __init__(self, version): super(DepictionGenerator, self).__init__() self.version = version self.root = os.path.dirname(os.path.abspath(__file__)) + "/../" self.PackageLister = PackageLister(self.version) def CleanUp(self): """ Cleans up some stuff. """ # Remove all Silica-generated folders in docs/ except for docs/pkg/. try: shutil.rmtree(root + "docs/api") except Exception: pass try: shutil.rmtree(root + "docs/assets") except Exception: pass try: shutil.rmtree(root + "docs/depiction") except Exception: pass try: shutil.rmtree(root + "docs/web") except Exception: pass # Delete all Silica-generated files in root. try: os.remove(self.root + "docs/404.html") except Exception: pass try: os.remove(self.root + "docs/CNAME") except Exception: pass try: os.remove(self.root + "docs/CydiaIcon.png") except Exception: pass try: os.remove(self.root + "docs/index.html") except Exception: pass try: os.remove(self.root + "docs/Packages") except Exception: pass try: os.remove(self.root + "docs/Packages.bz2") except Exception: pass try: os.remove(self.root + "docs/Packages.xz") except Exception: pass try: os.remove(self.root + "docs/Release") except Exception: pass try: os.remove(self.root + "docs/sileo-featured.json") except Exception: pass # Clean up temp. try: shutil.rmtree(root + "temp/") except Exception: pass def RenderPackageHTML(self, tweak_data): """ Renders a package's depiction. Object tweak_data: A single index of a "tweak release" object. """ with open(self.root + "Styles/tweak.mustache", "r") as content_file: index = content_file.read() replacements = DepictionGenerator.RenderDataHTML(self) try: replacements['tweak_name'] = tweak_data['name'] except: PackageLister.ErrorReporter( self, "Configuration Error!", "You are missing a package " "name in its index.json. Make sure this and other required properties are set." ) try: replacements['tweak_developer'] = tweak_data['developer'][ 'name'] replacements['tweak_compatibility'] = "iOS " + tweak_data[ 'works_min'] + " to " + tweak_data['works_max'] replacements['tweak_version'] = tweak_data['version'] replacements['tweak_section'] = tweak_data['section'] replacements['tweak_bundle_id'] = tweak_data['bundle_id'] replacements['works_min'] = tweak_data['works_min'] replacements['works_max'] = tweak_data['works_max'] replacements['tweak_tagline'] = tweak_data['tagline'] except: PackageLister.ErrorReporter( self, "Configuration Error!", "You are missing an essential " "property in " + tweak_data['name'] + "'s index.json. Make sure developer, version, section, " "bundle id, and tagline are set properly.") replacements[ 'tweak_carousel'] = DepictionGenerator.ScreenshotCarousel( self, tweak_data) replacements[ 'tweak_changelog'] = DepictionGenerator.RenderChangelogHTML( self, tweak_data) replacements['footer'] = DepictionGenerator.RenderFooter(self) try: if tweak_data['source'] != "": replacements['source'] = tweak_data['source'] except Exception: pass try: replacements['tint_color'] = tweak_data['tint'] except Exception: try: repo_settings = PackageLister.GetRepoSettings(self) replacements['tint_color'] = repo_settings['tint'] except Exception: replacements['tint_color'] = "#2cb1be" try: with open( self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: raw_md = md_file.read() desc_md = mistune.markdown(raw_md) replacements['tweak_description'] = desc_md except Exception: replacements['tweak_description'] = tweak_data['tagline'] # tweak_carousel return pystache.render(index, replacements) def RenderPackageNative(self, tweak_data): """ Renders a package's depiction using Sileo's "native depiction" format. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) try: tint = tweak_data['tint'] except Exception: try: tint = repo_settings['tint'] except Exception: tint = "#2cb1be" try: with open( self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: md_txt = md_file.read() except Exception: md_txt = tweak_data['tagline'] date = datetime.datetime.now().strftime("%Y-%m-%d") screenshot_obj = [] image_list = self.PackageLister.GetScreenshots(tweak_data) subfolder = PackageLister.FullPathCname(self, repo_settings) if len(image_list) > 0: for image in image_list: screenshot_entry = { "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/screenshot/" + image, "accessibilityText": "Screenshot" } screenshot_obj.append(screenshot_entry) # The following code is evil, but is actually easier to maintain. My humblest apologies. screenshot_view_title = "DepictionHeaderView" screenshot_view_carousel = "DepictionScreenshotsView" else: # The following code is evil, but is actually easier to maintain. My humblest apologies. screenshot_view_title = "HiddenDepictionHeaderView" screenshot_view_carousel = "HiddenDepictionScreenshotsView" changelog = DepictionGenerator.RenderNativeChangelog(self, tweak_data) screenshot_size = PackageLister.GetScreenshotSize(self, tweak_data) depiction = { "minVersion": "0.1", "headerImage": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/banner.png", "tintColor": tint, "tabs": [{ "tabname": "Details", "views": [{ "class": screenshot_view_carousel, "screenshots": screenshot_obj, "itemCornerRadius": 8, "itemSize": screenshot_size }, { "markdown": md_txt, "useSpacing": "true", "class": "DepictionMarkdownView" }, { "class": "DepictionSpacerView" }, { "class": "DepictionHeaderView", "title": "Information", }, { "class": "DepictionTableTextView", "title": "Developer", "text": tweak_data['developer']['name'] }, { "class": "DepictionTableTextView", "title": "Version", "text": tweak_data['version'] }, { "class": "DepictionTableTextView", "title": "Compatibility", "text": "iOS " + tweak_data['works_min'] + " to " + tweak_data['works_max'] }, { "class": "DepictionTableTextView", "title": "Section", "text": tweak_data['section'] }, { "class": "DepictionSpacerView" }, { "class": "DepictionTableButtonView", "title": "Contact Support", "action": "depiction-https://" + repo_settings['cname'] + subfolder + "/depiction/native/help/" + tweak_data['bundle_id'] + ".json", "openExternal": "true", "tintColor": tint }, { "class": "DepictionLabelView", "text": DepictionGenerator.RenderFooter(self), "textColor": "#999999", "fontSize": "10.0", "alignment": 1 }], "class": "DepictionStackView" }, { "tabname": "Changelog", "views": changelog, "class": "DepictionStackView" }], "class": "DepictionTabView" } blank = {"class": "DepictionSpacerView"} try: if tweak_data['source'] != "": source_btn = { "class": "DepictionTableButtonView", "title": "View Source Code", "action": tweak_data['source'], "openExternal": "true", "tintColor": tint } depiction['tabs'][0]['views'].insert(8, source_btn) depiction['tabs'][0]['views'].insert(8, blank) pass except Exception: pass return json.dumps(depiction, separators=(',', ':')) def RenderNativeChangelog(self, tweak_data): """ Generates a changelog for use in native depictions. Object tweak_data: A single index of a "tweak release" object. """ try: changelog = [] for version in tweak_data['changelog'][::-1]: ver_entry = { "class": "DepictionMarkdownView", "markdown": "#### Version {0}\n\n{1}".format(version['version'], version['changes']), } changelog.append(ver_entry) changelog.append({ "class": "DepictionLabelView", "text": DepictionGenerator.RenderFooter(self), "textColor": "#999999", "fontSize": "10.0", "alignment": 1 }) return changelog except Exception: return [{ "class": "DepictionHeaderView", "title": "Changelog" }, { "class": "DepictionMarkdownView", "markdown": "This package has no changelog.", }, { "class": "DepictionLabelView", "text": DepictionGenerator.RenderFooter(self), "textColor": "#999999", "fontSize": "10.0", "alignment": 1 }] def ChangelogEntry(self, version, raw_md): """ Generates a div for changelog entries. String version: The version number. String raw_md: The changelog entry text (Markdown-compatible). """ return '''<div class="changelog_entry"> <h4>{0}</h4> <div class="md_view">{1}</div> </div>'''.format(version, mistune.markdown(raw_md)) def RenderChangelogHTML(self, tweak_data): """ Generates a div of changelog entries. Object tweak_data: A single index of a "tweak release" object. """ element = "" try: for version in tweak_data['changelog'][::-1]: element += DepictionGenerator.ChangelogEntry( self, version['version'], version['changes']) return element except Exception: return "This package has no changelog." def RenderIndexHTML(self): """ Renders the home page (index.html). """ repo_settings = PackageLister.GetRepoSettings(self) with open(self.root + "Styles/index.mustache", "r") as content_file: index = content_file.read() replacements = DepictionGenerator.RenderDataHTML(self) replacements['tint_color'] = repo_settings['tint'] replacements['footer'] = DepictionGenerator.RenderFooter(self) replacements['tweak_release'] = PackageLister.GetTweakRelease(self) return pystache.render(index, replacements) def RenderFooter(self): """ Renders the footer. """ repo_settings = PackageLister.GetRepoSettings(self) data = DepictionGenerator.RenderDataHTML(self) try: footer = pystache.render(repo_settings['footer'], data) except Exception: footer = pystache.render( "Silica {{silica_version}} – Updated {{silica_compile_date}}", data) return footer def RenderDataBasic(self): """ Gets the value of basic repo data to pass to Pystache. """ repo_settings = PackageLister.GetRepoSettings(self) with open(self.root + "Styles/settings.json", "r") as content_file: data = json.load(content_file) date = datetime.datetime.now().strftime("%Y-%m-%d") subfolder = PackageLister.FullPathCname(self, repo_settings) return { "silica_version": self.version, "silica_compile_date": date, "repo_name": data['name'], "repo_url": data['cname'] + subfolder, "repo_desc": data['description'], "repo_tint": data['tint'] } def RenderDataHTML(self): data = DepictionGenerator.RenderDataBasic(self) tweak_release = PackageLister.GetTweakRelease(self) data['repo_packages'] = DepictionGenerator.PackageEntryList( self, tweak_release) data['repo_carousel'] = DepictionGenerator.CarouselEntryList( self, tweak_release) return data def PackageEntry(self, name, author, icon, bundle_id): """ Generates a package entry div. String name: The package's name String author: The author's name String (URL) icon: A URL to an image of the package icon. Scope: HTML > Generation > Helpers """ if (bundle_id != "silica_do_not_hyperlink"): return '''<a class="subtle_link" href="depiction/web/{3}.html"><div class="package"> <img src="{0}"> <div class="package_info"> <p class="package_name">{1}</p> <p class="package_caption">{2}</p> </div> </div></a>'''.format(icon, name, author, bundle_id) else: return '''<div class="package"> <img src="{0}"> <div class="package_info"> <p class="package_name">{1}</p> <p class="package_caption">{2}</p> </div> </div>'''.format(icon, name, author) def ScreenshotCarousel(self, tweak_data): """ Generates a screenshot div. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) screenshot_div = "<div class=\"scroll_view\">" image_list = self.PackageLister.GetScreenshots(tweak_data) if (len(image_list) > 0): for image in image_list: screenshot_div += '''<img class="img_card" src="../../assets/{1}/screenshot/{2}">'''.format( repo_settings['cname'], tweak_data['bundle_id'], image) screenshot_div += "</div>" else: screenshot_div = "" return screenshot_div def CarouselEntry(self, name, banner, bundle_id): """ Generates a card to be used in Featured carousels. String name: The package's name String (URL) banner: A URL to an image of the package banner. """ if len(name) > 18: name = name[:18] + "…" return '''<a href="depiction/web/{0}.html" style="background-image: url({1})" class="card"> <p>{2}</p> </a>'''.format(bundle_id, banner, name) def NativeFeaturedCarousel(self, tweak_release): """ Generate a sileo-featured.json file for featured packages. Object carousel_entry_list: A "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) subfolder = PackageLister.FullPathCname(self, repo_settings) banners = [] for package in tweak_release: try: if package['featured'].lower() == "true": ar_el = { "package": package['bundle_id'], "title": package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) except Exception: pass if len(banners) == 0: try: featured_int = random.randint(0, (len(tweak_release) - 1)) except Exception: PackageLister.ErrorReporter( self, "Configuration Error!", "You have no packages added to this repo. " "Make sure a folder is created at \"" + self.version + "/Packages\" that contains folders with " "package data inside of them and run Silica again.") featured_package = tweak_release[featured_int] ar_el = { "package": featured_package['bundle_id'], "title": featured_package['name'], "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + featured_package['bundle_id'] + "/banner.png", "hideShadow": "false" } banners.append(ar_el) featured_json = { "class": "FeaturedBannersView", "itemSize": "{263, 148}", "itemCornerRadius": 8, "banners": banners } return json.dumps(featured_json, separators=(',', ':')) def PackageEntryList(self, tweak_release): """ Generate a user-friendly list of packages on the repo. Object tweak_release: A "tweak release" object. """ list_el = "" for package in tweak_release: list_el += DepictionGenerator.PackageEntry( self, package['name'], package['developer']['name'], "assets/" + package['bundle_id'] + "/icon.png", package['bundle_id']) return list_el def CarouselEntryList(self, tweak_release): """ Generate a carousel of featured packages on the repo. Object tweak_release: A "tweak release" object. """ list_el = "" for package in tweak_release: try: if package['featured'].lower() == "true": list_el += DepictionGenerator.CarouselEntry( self, package['name'], "assets/" + package['bundle_id'] + "/banner.png", package['bundle_id']) except Exception: pass if list_el == "": try: featured_int = random.randint(0, (len(tweak_release) - 1)) except Exception: PackageLister.ErrorReporter( self, "Configuration Error!", "You have no packages added to this repo." " Make sure a folder is created at \"" + self.version + "/Packages\" that contains folders with package data inside of them and run Silica again." ) featured_package = tweak_release[featured_int] list_el += DepictionGenerator.CarouselEntry( self, featured_package['name'], "assets/" + featured_package['bundle_id'] + "/banner.png", featured_package['bundle_id']) return list_el def SilicaAbout(self): """ Returns a JSON object that describes information about the Silica install. """ compile_date = datetime.datetime.now().isoformat() try: upstream_url = check_output( ["git", "config", "--get", "remote.origin.url"], cwd=self.root).decode("utf-8") except Exception: upstream_url = "undefined" return { "software": "Silica", "version": self.version, "compile_date": compile_date, "upstream_url": upstream_url } def RenderNativeHelp(self, tweak_data): """ Generates a help view for Sileo users. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) try: tint = tweak_data['tint'] except Exception: try: tint = repo_settings['tint'] except Exception: tint = "#2cb1be" view = [] try: if tweak_data['developer']['email']: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " + tweak_data['developer']['name'] + ", the developer, via e-mail." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Developer", "action": "mailto:" + tweak_data['developer']['email'], "openExternal": "true", "tintColor": tint }) except Exception: try: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you can contact " + tweak_data['developer']['name'] + ", who is the developer. Sadly, we don't know their email." }) except Exception: view.append({ "class": "DepictionMarkdownView", "markdown": "The developer of the package \"" + tweak_data['name'] + "\" is not known. Try contacting the repo owner for more information." }) try: if tweak_data['social']: view.append({ "class": "DepictionMarkdownView", "markdown": "You can also contact " + tweak_data['developer']['name'] + " using the following" + " sites:" }) for entry in tweak_data['social']: view.append({ "class": "DepictionTableButtonView", "title": entry['name'], "action": entry['url'], "openExternal": "true", "tintColor": tint }) except Exception: pass try: if tweak_data['maintainer']['email']: view.append({ "class": "DepictionMarkdownView", "markdown": tweak_data['maintainer']['name'] + " is the maintainer of the package \"" + tweak_data['name'] + "\". Please contact them via email for any questions on this" " version of the package." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Maintainer", "action": "mailto:" + tweak_data['maintainer']['email'], "openExternal": "true", "tintColor": tint }) except Exception: try: view.append({ "class": "DepictionMarkdownView", "markdown": "If you need help with \"" + tweak_data['name'] + "\", you should contact " + tweak_data['maintainer']['name'] + ", who is the package's current maintainer. Sadly, we don't know their email." }) except Exception: pass view.append({ "class": "DepictionMarkdownView", "markdown": "If you found a mistake in the depiction or cannot download the package, you can reach out" + " to the maintainer of the \"" + repo_settings['name'] + "\" repo, " + repo_settings['maintainer']['name'] + "." }) view.append({ "class": "DepictionTableButtonView", "title": "Email Repo Maintainer", "action": "mailto:" + repo_settings['maintainer']['email'], "openExternal": "true", "tintColor": tint }) try: if repo_settings['social']: view.append({ "class": "DepictionMarkdownView", "markdown": "You can also contact the repo owner via the following" + " sites:" }) for entry in repo_settings['social']: view.append({ "class": "DepictionTableButtonView", "title": entry['name'], "action": entry['url'], "openExternal": "true", "tintColor": tint }) except Exception: pass return json.dumps( { "class": "DepictionStackView", "tintColor": tint, "title": "Contact Support", "views": view }, separators=(',', ':'))
def RenderPackageNative(self, tweak_data): """ Renders a package's depiction using Sileo's "native depiction" format. Object tweak_data: A single index of a "tweak release" object. """ repo_settings = PackageLister.GetRepoSettings(self) try: tint = tweak_data['tint'] except Exception: try: tint = repo_settings['tint'] except Exception: tint = "#2cb1be" try: with open( self.root + "docs/assets/" + tweak_data['bundle_id'] + "/description.md", "r") as md_file: md_txt = md_file.read() except Exception: md_txt = tweak_data['tagline'] date = datetime.datetime.now().strftime("%Y-%m-%d") screenshot_obj = [] image_list = self.PackageLister.GetScreenshots(tweak_data) subfolder = PackageLister.FullPathCname(self, repo_settings) if len(image_list) > 0: for image in image_list: screenshot_entry = { "url": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/screenshot/" + image, "accessibilityText": "Screenshot" } screenshot_obj.append(screenshot_entry) # The following code is evil, but is actually easier to maintain. My humblest apologies. screenshot_view_title = "DepictionHeaderView" screenshot_view_carousel = "DepictionScreenshotsView" else: # The following code is evil, but is actually easier to maintain. My humblest apologies. screenshot_view_title = "HiddenDepictionHeaderView" screenshot_view_carousel = "HiddenDepictionScreenshotsView" changelog = DepictionGenerator.RenderNativeChangelog(self, tweak_data) screenshot_size = PackageLister.GetScreenshotSize(self, tweak_data) depiction = { "minVersion": "0.1", "headerImage": "https://" + repo_settings['cname'] + subfolder + "/assets/" + tweak_data['bundle_id'] + "/banner.png", "tintColor": tint, "tabs": [{ "tabname": "Details", "views": [{ "class": screenshot_view_carousel, "screenshots": screenshot_obj, "itemCornerRadius": 8, "itemSize": screenshot_size }, { "markdown": md_txt, "useSpacing": "true", "class": "DepictionMarkdownView" }, { "class": "DepictionSpacerView" }, { "class": "DepictionHeaderView", "title": "Information", }, { "class": "DepictionTableTextView", "title": "Developer", "text": tweak_data['developer']['name'] }, { "class": "DepictionTableTextView", "title": "Version", "text": tweak_data['version'] }, { "class": "DepictionTableTextView", "title": "Compatibility", "text": "iOS " + tweak_data['works_min'] + " to " + tweak_data['works_max'] }, { "class": "DepictionTableTextView", "title": "Section", "text": tweak_data['section'] }, { "class": "DepictionSpacerView" }, { "class": "DepictionTableButtonView", "title": "Contact Support", "action": "depiction-https://" + repo_settings['cname'] + subfolder + "/depiction/native/help/" + tweak_data['bundle_id'] + ".json", "openExternal": "true", "tintColor": tint }, { "class": "DepictionLabelView", "text": DepictionGenerator.RenderFooter(self), "textColor": "#999999", "fontSize": "10.0", "alignment": 1 }], "class": "DepictionStackView" }, { "tabname": "Changelog", "views": changelog, "class": "DepictionStackView" }], "class": "DepictionTabView" } blank = {"class": "DepictionSpacerView"} try: if tweak_data['source'] != "": source_btn = { "class": "DepictionTableButtonView", "title": "View Source Code", "action": tweak_data['source'], "openExternal": "true", "tintColor": tint } depiction['tabs'][0]['views'].insert(8, source_btn) depiction['tabs'][0]['views'].insert(8, blank) pass except Exception: pass return json.dumps(depiction, separators=(',', ':'))
def CreateDEB(self, bundle_id, recorded_version): """ Creates a DEB from information stored in the "temp" folder. String bundle_id: The bundle id of the package to compress. String recorded_version: Object tweak_release: A "tweak release" object. """ # TODO: Find a Python-based method to safely delete all DS_Store files. call(["find", ".", "-name", ".DS_Store", "-delete"], cwd=self.root + "temp/" + bundle_id) # Remove .DS_Store. Kinda finicky. for file_name in os.listdir(self.root + "temp/" + bundle_id): if file_name.endswith(".deb"): # Check if the DEB is a newer version deb = Dpkg(self.root + "temp/" + bundle_id + "/" + file_name) if Dpkg.compare_versions(recorded_version, deb.version) == -1: # Update package stuff package_name = PackageLister.BundleIdToDirName(self, bundle_id) with open(self.root + "Packages/" + package_name + "/silica_data/index.json", "r") as content_file: update_json = json.load(content_file) update_json['version'] = deb.version changelog_entry = input("The DEB provided for \"" + update_json['name'] + "\" has a new version available (" + recorded_version + " -> " + deb.version + "). What changed in this version?\n(Add multiple lines" + " by using newline characters [\\n\\n] and use valid Markdown syntax): " ) try: update_json['changelog'].append({ "version": deb.version, "changes": changelog_entry }) except Exception: update_json['changelog'] = { "version": deb.version, "changes": changelog_entry } # Get human-readable folder name folder = PackageLister.BundleIdToDirName(self, bundle_id) deb_path = self.root + "Packages/" + folder + "/" + file_name # Extract Control file and scripts from DEB DpkgPy.control_extract(self, deb_path, self.root + "Packages/" + folder + "/silica_data/scripts/") # Remove the Control; it's not needed. os.remove(self.root + "Packages/" + folder + "/silica_data/scripts/Control") if not os.listdir(self.root + "Packages/" + folder + "/silica_data/scripts/"): os.rmdir(self.root + "Packages/" + folder + "/silica_data/scripts/") return_str = json.dumps(update_json) print("Updating package index.json...") PackageLister.CreateFile(self, "Packages/" + package_name + "/silica_data/index.json", return_str) pass DpkgPy.extract(self, self.root + "temp/" + bundle_id + "/" + file_name, self.root + "temp/" + bundle_id) os.remove(self.root + "temp/" + bundle_id + "/" + file_name) os.remove(self.root + "temp/" + bundle_id + "/control") else: # TODO: Update DpkgPy to generate DEB files without dependencies (for improved win32 support) # If the version is consistent, then assume the package is unchanged. Don't regenerate it. try: docs_deb = Dpkg(self.root + "docs/pkg/" + bundle_id + ".deb") if docs_deb.version == recorded_version: shutil.copy(self.root + "docs/pkg/" + bundle_id + ".deb", self.root + "temp/" + bundle_id + ".deb") call_result = 0; else: # Sneaky swap. call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB except: call_result = call(["dpkg-deb", "-b", "-Zgzip", self.root + "temp/" + bundle_id], cwd=self.root + "temp/") # Compile DEB if call_result != 0: # Did we run within WSL? if "Microsoft" in platform.release(): PackageLister.ErrorReporter(self, "Platform Error!", "dpkg-deb failed to run. " "This is due to improper configuration of WSL. Please check the Silcia README for " "how to set up WSL for dpkg-deb.") else: PackageLister.ErrorReporter(self, "Platform Error!", "dpkg-deb failed to run. " "This may be due to a faulty system configuration.")