Compare commits

...

52 Commits

Author SHA1 Message Date
f4e6cd4ab0 Update README.md 2025-05-06 06:51:29 +00:00
c90d3071ea Update README.md 2025-05-06 06:51:06 +00:00
9de735aace Update package 2025-04-25 10:05:20 +02:00
786a72126c fix attribute error 2025-04-25 10:05:17 +02:00
70022a9b6c Update package 2025-04-25 10:03:02 +02:00
1f7b68e432 potential fix for same window preview 2025-04-25 10:02:58 +02:00
507a7e5d92 Update package 2025-04-25 10:00:08 +02:00
cc5d737c16 add logging for debugging 2025-04-25 10:00:03 +02:00
a7116d7206 Update package 2025-04-25 09:55:16 +02:00
4563a0f653 potentially fixes view in same window 2025-04-25 09:55:12 +02:00
e7a15ea070 Update package 2025-04-25 09:51:21 +02:00
34f8b3d733 open view in current window not in new one 2025-04-25 09:51:10 +02:00
e6a880d2a4 Merge branch 'master' of git.0x42.cloud:christian.morpurgo/MarkdownLivePreview 2025-04-24 20:33:02 +02:00
9cb5e2087f Update package 2025-04-24 20:32:52 +02:00
20dc7da4e8 Update package 2025-04-24 20:32:22 +02:00
0413ecca4b fix resourcxe path if repository name is different 2025-04-24 20:32:19 +02:00
37660abe64 Update repository.json 2025-04-24 18:29:07 +00:00
c2093e19aa Update package 2025-04-24 20:25:57 +02:00
b9ee2819e3 adjust scxale added more tags 2025-04-24 20:25:49 +02:00
99a4f21be3 Update package 2025-04-24 20:20:12 +02:00
edb424de99 font scale fix 2025-04-24 20:20:08 +02:00
d89e34f3e2 Update package 2025-04-24 20:18:52 +02:00
ecfd19dd85 potential fix for font scaling 2025-04-24 20:18:48 +02:00
69c722a933 Update package 2025-04-24 20:14:13 +02:00
7c37a9e413 potential fix for fontscaling 2025-04-24 20:14:10 +02:00
89d712fcce Update package 2025-04-24 20:11:22 +02:00
9f243f09c9 fix 2025-04-24 20:11:19 +02:00
ce768dce10 Update package 2025-04-24 20:09:01 +02:00
731f2def96 loggin 2025-04-24 20:08:56 +02:00
b3ffe8bf55 Update package 2025-04-24 20:05:55 +02:00
8576d4a631 Update package 2025-04-24 20:02:45 +02:00
49329d1f64 potential fix for font_scale 2025-04-24 20:02:39 +02:00
21b67f5b86 Update package 2025-04-24 20:00:49 +02:00
837979232e fix because we are using python 3.3 sic 2025-04-24 20:00:38 +02:00
547225ac4d Update package 2025-04-24 19:59:00 +02:00
e05516ab22 log font_scale 2025-04-24 19:58:47 +02:00
bbb90a8a97 Update package 2025-04-24 19:55:01 +02:00
598e22002b potential fix for font scale not working 2025-04-24 19:54:50 +02:00
e81b359294 Update package 2025-04-24 19:48:09 +02:00
8709e88fbd Update package 2025-04-24 19:46:51 +02:00
b94ad5856d fix import 2025-04-24 19:46:39 +02:00
3004ab4b41 Update package 2025-04-24 19:43:26 +02:00
14f6474cd5 fixing import error 2025-04-24 19:43:14 +02:00
d0323405c0 Update package 2025-04-24 19:40:38 +02:00
661f5b8911 fix impoert for soupsieve 2025-04-24 19:40:29 +02:00
3947b4cc4d Update package 2025-04-24 19:36:33 +02:00
2b35cf5000 add test_import to be ignored on export 2025-04-24 19:36:25 +02:00
72c684e89c Update package 2025-04-24 19:32:11 +02:00
ae1ea101d2 update package 2025-04-24 19:32:01 +02:00
3dd7b5a18d made bs4 and soupsieve standalone in this project 2025-04-24 19:26:20 +02:00
aefb27614f fully make bs4 and soupsieve standalone in the project 2025-04-24 17:39:41 +02:00
ed336866ee new build 2025-04-24 17:07:15 +02:00
23 changed files with 353 additions and 119 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
docs/ export-ignore docs/ export-ignore
resources/ resources/
!resources/*.base64 !resources/*.base64
test_imports.py export-ignore
MarkdownLivePreview.sublime-package export-ignore

View File

@ -51,7 +51,7 @@ def plugin_loaded():
resources["stylesheet"] = get_resource("stylesheet.css") resources["stylesheet"] = get_resource("stylesheet.css")
# FIXME: how could we make this setting update without restarting sublime text # FIXME: how could we make this setting update without restarting sublime text
# and not loading it every update as well # and not loading it every update as well
DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES) DELAY = get_settings().get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default
class MdlpInsertCommand(sublime_plugin.TextCommand): class MdlpInsertCommand(sublime_plugin.TextCommand):
@ -62,6 +62,7 @@ class MdlpInsertCommand(sublime_plugin.TextCommand):
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand): class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
def run(self, edit): def run(self, edit):
print("--- MarkdownLivePreview: OpenMarkdownPreviewCommand running ---")
""" If the file is saved exists on disk, we close it, and reopen it in a new """ If the file is saved exists on disk, we close it, and reopen it in a new
window. Otherwise, we copy the content, erase it all (to close the file without window. Otherwise, we copy the content, erase it all (to close the file without
a dialog) and re-insert it into a new view into a new window """ a dialog) and re-insert it into a new view into a new window """
@ -69,22 +70,30 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
original_view = self.view original_view = self.view
original_window_id = original_view.window().id() original_window_id = original_view.window().id()
file_name = original_view.file_name() file_name = original_view.file_name()
print("--- MarkdownLivePreview: Original view ID: {}, File: {}, Window ID: {} ---".format(original_view.id(), file_name, original_window_id))
syntax_file = original_view.settings().get("syntax") syntax_file = original_view.settings().get("syntax")
if file_name: # don't close the original view; keep it in your main window:
original_view.close() # if file_name:
else: # original_view.close()
# the file isn't saved, we need to restore the content manually # else:
# # the file isn't saved, we need to restore the content manually
# total_region = sublime.Region(0, original_view.size())
# content = original_view.substr(total_region)
# original_view.erase(edit, total_region)
# original_view.close()
# # FIXME: save the document to a temporary file, so that if we crash,
# # the user doesn't lose what he wrote
if not file_name:
# If the file isn't saved, we still need the content for the new view
total_region = sublime.Region(0, original_view.size()) total_region = sublime.Region(0, original_view.size())
content = original_view.substr(total_region) content = original_view.substr(total_region)
original_view.erase(edit, total_region) print("--- MarkdownLivePreview: Unsaved file, content length: {} ---".format(len(content)))
original_view.close()
# FIXME: save the document to a temporary file, so that if we crash,
# the user doesn't lose what he wrote
sublime.run_command("new_window") # instead of making a new window, grab your existing one:
preview_window = sublime.active_window() preview_window = original_view.window()
print("--- MarkdownLivePreview: Using existing window ID: {} ---".format(preview_window.id()))
preview_window.run_command( preview_window.run_command(
"set_layout", "set_layout",
@ -100,20 +109,44 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
preview_view.set_scratch(True) preview_view.set_scratch(True)
preview_view.settings().set(PREVIEW_VIEW_INFOS, {}) preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
preview_view.set_name("Preview") preview_view.set_name("Preview")
print("--- MarkdownLivePreview: Created preview_view ID: {} ---".format(preview_view.id()))
# FIXME: hide number lines on preview # FIXME: hide number lines on preview
preview_window.focus_group(0) preview_window.focus_group(0)
if file_name: if file_name:
markdown_view = preview_window.open_file(file_name) markdown_view = preview_window.open_file(file_name)
print("--- MarkdownLivePreview: Opened markdown_view ID: {} for file: {} ---".format(markdown_view.id(), file_name))
else: else:
markdown_view = preview_window.new_file() markdown_view = preview_window.new_file()
markdown_view.run_command("mdlp_insert", {"point": 0, "string": content}) markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
markdown_view.set_scratch(True) markdown_view.set_scratch(True)
print("--- MarkdownLivePreview: Created new markdown_view ID: {} for unsaved content ---".format(markdown_view.id()))
markdown_view.set_syntax_file(syntax_file) markdown_view.set_syntax_file(syntax_file)
markdown_view.settings().set( markdown_view.settings().set(
MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id,}, MARKDOWN_VIEW_INFOS, {"original_window_id": original_window_id, "preview_view_id": preview_view.id(),},
) )
infos_to_log = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
print("--- MarkdownLivePreview: Stored infos on markdown_view {}: {} ---".format(markdown_view.id(), infos_to_log))
# Manually trigger the initial setup and render via the listener
# Use set_timeout_async to run it after the command finishes
print("--- MarkdownLivePreview: Scheduling setup_and_update_preview for md_view: {}, pv_view: {} ---".format(markdown_view.id(), preview_view.id()))
sublime.set_timeout_async(lambda: self.trigger_listener_setup(markdown_view.id(), preview_view.id()), 0)
# Helper method to find and call the listener instance method
# This is needed because the listener isn't easily accessible directly from the command instance
def trigger_listener_setup(self, md_view_id, pv_view_id):
print("--- MarkdownLivePreview: trigger_listener_setup running for md_view: {}, pv_view: {} ---".format(md_view_id, pv_view_id))
# Access the listener instance directly via its class variable
listener_instance = MarkdownLivePreviewListener.instance
if listener_instance:
print("--- MarkdownLivePreview: Found listener instance via class variable, calling setup_and_update_preview ---")
listener_instance.setup_and_update_preview(md_view_id, pv_view_id)
else:
# This should ideally not happen if the listener loaded correctly before the command ran
print("--- MarkdownLivePreview: ERROR: MarkdownLivePreviewListener.instance is None! Cannot trigger setup. ---")
def is_enabled(self): def is_enabled(self):
# FIXME: is this the best way there is to check if the current syntax is markdown? # FIXME: is this the best way there is to check if the current syntax is markdown?
@ -124,6 +157,7 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
class MarkdownLivePreviewListener(sublime_plugin.EventListener): class MarkdownLivePreviewListener(sublime_plugin.EventListener):
instance = None # Class variable to hold the single instance
phantom_sets = { phantom_sets = {
# markdown_view.id(): phantom set # markdown_view.id(): phantom set
@ -133,6 +167,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
# then, we update only if now() - last_update > DELAY # then, we update only if now() - last_update > DELAY
last_update = 0 last_update = 0
def __init__(self):
super().__init__() # Good practice to call super
MarkdownLivePreviewListener.instance = self
print("--- MarkdownLivePreview: Listener instance created and registered. ---")
# FIXME: maybe we shouldn't restore the file in the original window... # FIXME: maybe we shouldn't restore the file in the original window...
def on_pre_close(self, markdown_view): def on_pre_close(self, markdown_view):
@ -149,18 +188,30 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
if self.file_name is None: if self.file_name is None:
total_region = sublime.Region(0, markdown_view.size()) total_region = sublime.Region(0, markdown_view.size())
self.content = markdown_view.substr(total_region) self.content = markdown_view.substr(total_region)
markdown_view.erase(edit, total_region)
else: else:
self.content = None self.content = None
def on_load_async(self, markdown_view): def on_load_async(self, markdown_view):
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos: if not infos:
# print("--- MarkdownLivePreview: on_load_async ignored for view {} - no infos ---".format(markdown_view.id())) # Optional: very verbose
return return
preview_view = markdown_view.window().active_view_in_group(1) print("--- MarkdownLivePreview: on_load_async triggered for markdown_view {} ---".format(markdown_view.id()))
preview_view_id = infos.get("preview_view_id")
if not preview_view_id:
print("--- MarkdownLivePreview: ERROR in on_load_async: No preview_view_id found in infos: {} ---".format(infos))
return # Should not happen if setup was correct
self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view) preview_view = sublime.View(preview_view_id)
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in on_load_async: Preview view {} is no longer valid ---".format(preview_view_id))
return # Preview view was closed before loading finished
print("--- MarkdownLivePreview: on_load_async found valid preview_view {} ---".format(preview_view.id()))
# PhantomSet creation is now handled by setup_and_update_preview triggered from the command
# self.phantom_sets[markdown_view.id()] = sublime.PhantomSet(preview_view)
# print("--- MarkdownLivePreview: PhantomSet created in on_load_async for preview_view {} ---".format(preview_view.id())) # Keep log commented
self._update_preview(markdown_view) self._update_preview(markdown_view)
def on_close(self, markdown_view): def on_close(self, markdown_view):
@ -170,13 +221,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
if not infos: if not infos:
return return
assert ( if markdown_view.id() in self.phantom_sets:
markdown_view.id() == self.markdown_view.id()
), "pre_close view.id() != close view.id()"
del self.phantom_sets[markdown_view.id()] del self.phantom_sets[markdown_view.id()]
self.preview_window.run_command("close_window") # don't close the entire window—just let the user close the preview tab:
# self.preview_window.run_command("close_window")
# find the window with the right id # find the window with the right id
original_window = next( original_window = next(
@ -207,14 +256,17 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
# @min_time_between_call(.5) # @min_time_between_call(.5)
def on_modified_async(self, markdown_view): def on_modified_async(self, markdown_view):
# print("--- MarkdownLivePreview: on_modified_async triggered for view {} ---".format(markdown_view.id())) # Optional: very verbose
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos: if not infos:
return return
print("--- MarkdownLivePreview: Scheduling update for markdown_view {} ---".format(markdown_view.id()))
# we schedule an update, which won't run if an # we schedule an update, which won't run if an
sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY) sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY)
def _update_preview(self, markdown_view): def _update_preview(self, markdown_view):
print("--- MarkdownLivePreview: _update_preview called for markdown_view {} ---".format(markdown_view.id()))
# if the buffer id is 0, that means that the markdown_view has been closed # if the buffer id is 0, that means that the markdown_view has been closed
# This check is needed since a this function is used as a callback for when images # This check is needed since a this function is used as a callback for when images
# are loaded from the internet (ie. it could finish loading *after* the user # are loaded from the internet (ie. it could finish loading *after* the user
@ -223,25 +275,53 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
settings = get_settings() settings = get_settings()
delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # Provide default
font_scale = settings.get(SETTING_FONT_SCALE, 1.0) # Provide default font_scale = settings.get(SETTING_FONT_SCALE, 1.0) # Provide default
print("--- MarkdownLivePreview: Using font_scale: {} ---".format(font_scale))
if time.time() - self.last_update < delay / 1000: if time.time() - self.last_update < delay / 1000:
print("--- MarkdownLivePreview: Update skipped for view {} due to time delay ---".format(markdown_view.id()))
return return
if markdown_view.buffer_id() == 0: if markdown_view.buffer_id() == 0:
print("--- MarkdownLivePreview: Update skipped for view {}: buffer_id is 0 (view closed) ---".format(markdown_view.id()))
return return
# Check if the phantom set still exists for this view ID
if markdown_view.id() not in self.phantom_sets:
print("--- MarkdownLivePreview: Update skipped for view {}: No phantom set found ---".format(markdown_view.id()))
# View might have been closed between modification and update
return
print("--- MarkdownLivePreview: Update proceeding for view {} ---".format(markdown_view.id()))
self.last_update = time.time() self.last_update = time.time()
total_region = sublime.Region(0, markdown_view.size()) total_region = sublime.Region(0, markdown_view.size())
markdown = markdown_view.substr(total_region) markdown = markdown_view.substr(total_region)
print("--- MarkdownLivePreview: Read markdown content (length: {}) ---".format(len(markdown)))
preview_view = markdown_view.window().active_view_in_group(1) infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
if not infos:
print("--- MarkdownLivePreview: ERROR in _update_preview: No infos found for view {} ---".format(markdown_view.id()))
return # Should not happen
preview_view_id = infos.get("preview_view_id")
if not preview_view_id:
print("--- MarkdownLivePreview: ERROR in _update_preview: No preview_view_id found in infos: {} ---".format(infos))
return # Should not happen
preview_view = sublime.View(preview_view_id)
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in _update_preview: Preview view {} is no longer valid ---".format(preview_view_id))
return # Preview view was closed
print("--- MarkdownLivePreview: Found valid preview_view {} for update ---".format(preview_view.id()))
# Get viewport_width, default to a large value if view isn't ready # Get viewport_width, default to a large value if view isn't ready
viewport_extent = preview_view.viewport_extent() viewport_extent = preview_view.viewport_extent()
viewport_width = viewport_extent[0] if viewport_extent else 1024 viewport_width = viewport_extent[0] if viewport_extent else 1024
print("--- MarkdownLivePreview: Viewport width: {} ---".format(viewport_width))
basepath = os.path.dirname(markdown_view.file_name()) if markdown_view.file_name() else '.' # Handle unsaved files basepath = os.path.dirname(markdown_view.file_name()) if markdown_view.file_name() else '.' # Handle unsaved files
print("--- MarkdownLivePreview: Calling markdown2html with basepath: {} ---".format(basepath))
html = markdown2html( html = markdown2html(
markdown, markdown,
basepath, basepath,
@ -251,6 +331,10 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
font_scale, font_scale,
) )
# Truncate HTML safely for logging
html_preview = html[:100].replace('\n', ' ') # Avoid breaking log lines
print("--- MarkdownLivePreview: Generated HTML (starts with): {}... ---".format(html_preview))
self.phantom_sets[markdown_view.id()].update( self.phantom_sets[markdown_view.id()].update(
[ [
sublime.Phantom( sublime.Phantom(
@ -261,6 +345,32 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
) )
] ]
) )
print("--- MarkdownLivePreview: Updated phantoms in preview_view {} ---".format(preview_view.id()))
def setup_and_update_preview(self, markdown_view_id, preview_view_id):
print("--- MarkdownLivePreview: setup_and_update_preview called for md_view: {}, pv_view: {} ---".format(markdown_view_id, preview_view_id))
markdown_view = sublime.View(markdown_view_id)
preview_view = sublime.View(preview_view_id)
if not markdown_view.is_valid():
print("--- MarkdownLivePreview: ERROR in setup_and_update: markdown_view {} is not valid ---".format(markdown_view_id))
return
if not preview_view.is_valid():
print("--- MarkdownLivePreview: ERROR in setup_and_update: preview_view {} is not valid ---".format(preview_view_id))
return
# Create PhantomSet if it doesn't exist (shouldn't at this point)
if markdown_view_id not in self.phantom_sets:
print("--- MarkdownLivePreview: Creating PhantomSet for preview_view {} in setup ---".format(preview_view.id()))
self.phantom_sets[markdown_view_id] = sublime.PhantomSet(preview_view)
else:
# This case might occur if the command is run multiple times rapidly? Ensure it's associated correctly.
print("--- MarkdownLivePreview: Warning: PhantomSet already existed for markdown_view {} in setup. Re-associating with preview_view {}. ---".format(markdown_view_id, preview_view.id()))
self.phantom_sets[markdown_view_id].view = preview_view # Ensure it points to the correct view
# Trigger the first update
print("--- MarkdownLivePreview: Triggering initial _update_preview from setup ---")
self._update_preview(markdown_view)
def get_settings(): def get_settings():
@ -268,11 +378,15 @@ def get_settings():
def get_resource(resource): def get_resource(resource):
path = "Packages/MarkdownLivePreview/resources/" + resource package_name = __package__ # Get the current package name dynamically
abs_path = os.path.join(sublime.packages_path(), "..", path) path = "Packages/{}/resources/{}".format(package_name, resource)
# Original logic: check absolute path first (useful for unpacked development)
# Adjusted abs_path to use dynamic package_name
abs_path = os.path.join(sublime.packages_path(), package_name, "resources", resource)
if os.path.isfile(abs_path): if os.path.isfile(abs_path):
with open(abs_path, "r") as fp: with open(abs_path, "r") as fp:
return fp.read() return fp.read()
# Fallback to sublime.load_resource (works for packed and unpacked)
return sublime.load_resource(path) return sublime.load_resource(path)

Binary file not shown.

View File

@ -1,5 +1,25 @@
# MarkdownLivePreview # MarkdownLivePreview
## Acknowledgments
This project is a fork of [MarkdownLivePreview](https://github.com/math2001/MarkdownLivePreview) by **math2001**. I'm grateful for the original implementation, which provided a solid foundation for live Markdown preview in Sublime Text.
Many thanks to math2001 for the original code—this fork wouldn't have been possible without their work.
## Changes contained in this Fork
In this fork ([christian.morpurgo/MarkdownLivePreview](https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview)), I've made the following enhancements:
- **Bundled `bs4` and `soupsieve`**
Repackaged both libraries as standalone modules by rewriting their imports, resolving errors when Sublime Text 4 attempted to install these old versions as external dependencies.
- **`font_scale` option**
Added a new `font_scale: number` setting to allow users to increase or decrease the preview text size directly from their Sublime Text settings.
- **In-window preview**
Changed the preview behavior so that starting a preview reuses the current window instead of opening a new one.
A simple plugin to preview your markdown as you type right in Sublime Text. A simple plugin to preview your markdown as you type right in Sublime Text.
No dependencies! No dependencies!
@ -31,7 +51,7 @@ could be working on, then there are a bunch of `FIXME`s all over this package.
Just pick one and fix it :-) Just pick one and fix it :-)
``` ```
$ git clone https://github.com/math2001/MarkdownLivePreview $ git clone https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview
$ cd MarkdownLivePreview $ cd MarkdownLivePreview
$ grep -R FIXME $ grep -R FIXME
``` ```

6
create_package.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
git archive --format=zip --prefix="" --output=MarkdownLivePreview.sublime-package master
git add MarkdownLivePreview.sublime-package
git commit -m "Update package"
git push origin master

View File

@ -4,7 +4,7 @@ __license__ = "MIT"
from collections import defaultdict from collections import defaultdict
import itertools import itertools
import sys import sys
from bs4.element import ( from ..element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
ContentMetaAttributeValue, ContentMetaAttributeValue,
Stylesheet, Stylesheet,

View File

@ -7,13 +7,13 @@ __all__ = [
import warnings import warnings
import re import re
from bs4.builder import ( from . import (
PERMISSIVE, PERMISSIVE,
HTML, HTML,
HTML_5, HTML_5,
HTMLTreeBuilder, HTMLTreeBuilder,
) )
from bs4.element import ( from ..element import (
NamespacedAttribute, NamespacedAttribute,
nonwhitespace_re, nonwhitespace_re,
) )
@ -22,7 +22,7 @@ from html5lib.constants import (
namespaces, namespaces,
prefixes, prefixes,
) )
from bs4.element import ( from ..element import (
Comment, Comment,
Doctype, Doctype,
NavigableString, NavigableString,
@ -120,7 +120,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
if soup: if soup:
self.soup = soup self.soup = soup
else: else:
from bs4 import BeautifulSoup from .. import BeautifulSoup
# TODO: Why is the parser 'html.parser' here? To avoid an # TODO: Why is the parser 'html.parser' here? To avoid an
# infinite loop? # infinite loop?
self.soup = BeautifulSoup( self.soup = BeautifulSoup(
@ -166,7 +166,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
return TextNode(Comment(data), self.soup) return TextNode(Comment(data), self.soup)
def fragmentClass(self): def fragmentClass(self):
from bs4 import BeautifulSoup from .. import BeautifulSoup
# TODO: Why is the parser 'html.parser' here? To avoid an # TODO: Why is the parser 'html.parser' here? To avoid an
# infinite loop? # infinite loop?
self.soup = BeautifulSoup("", "html.parser") self.soup = BeautifulSoup("", "html.parser")
@ -184,7 +184,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
return treebuilder_base.TreeBuilder.getFragment(self).element return treebuilder_base.TreeBuilder.getFragment(self).element
def testSerializer(self, element): def testSerializer(self, element):
from bs4 import BeautifulSoup from .. import BeautifulSoup
rv = [] rv = []
doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$') doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$')

View File

@ -34,16 +34,16 @@ CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3
CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4 CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4
from bs4.element import ( from ..element import (
CData, CData,
Comment, Comment,
Declaration, Declaration,
Doctype, Doctype,
ProcessingInstruction, ProcessingInstruction,
) )
from bs4.dammit import EntitySubstitution, UnicodeDammit from ..dammit import EntitySubstitution, UnicodeDammit
from bs4.builder import ( from . import (
HTML, HTML,
HTMLTreeBuilder, HTMLTreeBuilder,
STRICT, STRICT,

View File

@ -14,14 +14,14 @@ except ImportError as e:
from io import BytesIO from io import BytesIO
from io import StringIO from io import StringIO
from lxml import etree from lxml import etree
from bs4.element import ( from ..element import (
Comment, Comment,
Doctype, Doctype,
NamespacedAttribute, NamespacedAttribute,
ProcessingInstruction, ProcessingInstruction,
XMLProcessingInstruction, XMLProcessingInstruction,
) )
from bs4.builder import ( from . import (
FAST, FAST,
HTML, HTML,
HTMLTreeBuilder, HTMLTreeBuilder,
@ -29,7 +29,7 @@ from bs4.builder import (
ParserRejectedMarkup, ParserRejectedMarkup,
TreeBuilder, TreeBuilder,
XML) XML)
from bs4.dammit import EncodingDetector from ..dammit import EncodingDetector
LXML = 'lxml' LXML = 'lxml'

View File

@ -6,9 +6,9 @@ __license__ = "MIT"
import cProfile import cProfile
from io import StringIO from io import StringIO
from html.parser import HTMLParser from html.parser import HTMLParser
import bs4 from . import BeautifulSoup as bs4
from bs4 import BeautifulSoup, __version__ from . import BeautifulSoup, __version__
from bs4.builder import builder_registry from .builder import builder_registry
import os import os
import pstats import pstats

View File

@ -9,14 +9,16 @@ import re
import sys import sys
import warnings import warnings
try: try:
import soupsieve # We are installed under the bs4 package
from soupsieve import *
except ImportError as e: except ImportError as e:
# We are installed standalone, or soupsieve is not installed.
soupsieve = None soupsieve = None
warnings.warn( warnings.warn(
'The soupsieve package is not installed. CSS selectors cannot be used.' 'The soupsieve package is not installed. CSS selectors cannot be used.'
) )
from bs4.formatter import ( from .formatter import (
Formatter, Formatter,
HTMLFormatter, HTMLFormatter,
XMLFormatter, XMLFormatter,
@ -380,7 +382,7 @@ class PageElement(object):
and not isinstance(new_child, NavigableString)): and not isinstance(new_child, NavigableString)):
new_child = NavigableString(new_child) new_child = NavigableString(new_child)
from bs4 import BeautifulSoup from . import BeautifulSoup
if isinstance(new_child, BeautifulSoup): if isinstance(new_child, BeautifulSoup):
# We don't want to end up with a situation where one BeautifulSoup # We don't want to end up with a situation where one BeautifulSoup
# object contains another. Insert the children one at a time. # object contains another. Insert the children one at a time.

View File

@ -1,4 +1,4 @@
from bs4.dammit import EntitySubstitution from .dammit import EntitySubstitution
class Formatter(EntitySubstitution): class Formatter(EntitySubstitution):
"""Describes a strategy to use when outputting a parse tree to a string. """Describes a strategy to use when outputting a parse tree to a string.

View File

@ -9,8 +9,8 @@ import copy
import functools import functools
import unittest import unittest
from unittest import TestCase from unittest import TestCase
from bs4 import BeautifulSoup from . import BeautifulSoup
from bs4.element import ( from .element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
Comment, Comment,
ContentMetaAttributeValue, ContentMetaAttributeValue,
@ -22,7 +22,7 @@ from bs4.element import (
Tag Tag
) )
from bs4.builder import HTMLParserTreeBuilder from .builder import HTMLParserTreeBuilder
default_builder = HTMLParserTreeBuilder default_builder = HTMLParserTreeBuilder
BAD_DOCUMENT = """A bare string BAD_DOCUMENT = """A bare string

View File

@ -3,21 +3,21 @@
import unittest import unittest
import warnings import warnings
from bs4 import BeautifulSoup from .. import BeautifulSoup
from bs4.builder import ( from ..builder import (
builder_registry as registry, builder_registry as registry,
HTMLParserTreeBuilder, HTMLParserTreeBuilder,
TreeBuilderRegistry, TreeBuilderRegistry,
) )
try: try:
from bs4.builder import HTML5TreeBuilder from ..builder import HTML5TreeBuilder
HTML5LIB_PRESENT = True HTML5LIB_PRESENT = True
except ImportError: except ImportError:
HTML5LIB_PRESENT = False HTML5LIB_PRESENT = False
try: try:
from bs4.builder import ( from ..builder import (
LXMLTreeBuilderForXML, LXMLTreeBuilderForXML,
LXMLTreeBuilder, LXMLTreeBuilder,
) )

View File

@ -3,12 +3,12 @@
import warnings import warnings
try: try:
from bs4.builder import HTML5TreeBuilder from ..builder import HTML5TreeBuilder
HTML5LIB_PRESENT = True HTML5LIB_PRESENT = True
except ImportError as e: except ImportError as e:
HTML5LIB_PRESENT = False HTML5LIB_PRESENT = False
from bs4.element import SoupStrainer from ..element import SoupStrainer
from bs4.testing import ( from ..testing import (
HTML5TreeBuilderSmokeTest, HTML5TreeBuilderSmokeTest,
SoupTest, SoupTest,
skipIf, skipIf,

View File

@ -3,9 +3,9 @@ trees."""
from pdb import set_trace from pdb import set_trace
import pickle import pickle
from bs4.testing import SoupTest, HTMLTreeBuilderSmokeTest from ..testing import SoupTest, HTMLTreeBuilderSmokeTest
from bs4.builder import HTMLParserTreeBuilder from ..builder import HTMLParserTreeBuilder
from bs4.builder._htmlparser import BeautifulSoupHTMLParser from ..builder._htmlparser import BeautifulSoupHTMLParser
class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest): class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest):

View File

@ -12,16 +12,16 @@ except ImportError as e:
LXML_VERSION = (0,) LXML_VERSION = (0,)
if LXML_PRESENT: if LXML_PRESENT:
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
from bs4 import ( from .. import (
BeautifulSoup, BeautifulSoup,
BeautifulStoneSoup, BeautifulStoneSoup,
) )
from bs4.element import Comment, Doctype, SoupStrainer from ..element import Comment, Doctype, SoupStrainer
from bs4.testing import skipIf from ..testing import skipIf
from bs4.tests import test_htmlparser from . import test_htmlparser
from bs4.testing import ( from ..testing import (
HTMLTreeBuilderSmokeTest, HTMLTreeBuilderSmokeTest,
XMLTreeBuilderSmokeTest, XMLTreeBuilderSmokeTest,
SoupTest, SoupTest,

View File

@ -7,17 +7,17 @@ import unittest
import sys import sys
import tempfile import tempfile
from bs4 import ( from .. import (
BeautifulSoup, BeautifulSoup,
BeautifulStoneSoup, BeautifulStoneSoup,
GuessedAtParserWarning, GuessedAtParserWarning,
MarkupResemblesLocatorWarning, MarkupResemblesLocatorWarning,
) )
from bs4.builder import ( from ..builder import (
TreeBuilder, TreeBuilder,
ParserRejectedMarkup, ParserRejectedMarkup,
) )
from bs4.element import ( from ..element import (
CharsetMetaAttributeValue, CharsetMetaAttributeValue,
Comment, Comment,
ContentMetaAttributeValue, ContentMetaAttributeValue,
@ -27,13 +27,13 @@ from bs4.element import (
NavigableString, NavigableString,
) )
import bs4.dammit from ..dammit import *
from bs4.dammit import ( from ..dammit import (
EntitySubstitution, EntitySubstitution,
UnicodeDammit, UnicodeDammit,
EncodingDetector, EncodingDetector,
) )
from bs4.testing import ( from ..testing import (
default_builder, default_builder,
SoupTest, SoupTest,
skipIf, skipIf,
@ -41,7 +41,7 @@ from bs4.testing import (
import warnings import warnings
try: try:
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
LXML_PRESENT = True LXML_PRESENT = True
except ImportError as e: except ImportError as e:
LXML_PRESENT = False LXML_PRESENT = False
@ -120,11 +120,10 @@ class TestConstructor(SoupTest):
def feed(self, *args, **kwargs): def feed(self, *args, **kwargs):
raise ParserRejectedMarkup("Nope.") raise ParserRejectedMarkup("Nope.")
def prepare_markup(self, *args, **kwargs): def prepare_markup(self, markup, *args, **kwargs):
# We're going to try two different ways of preparing this markup, # We're going to try two different ways of preparing this markup,
# but feed() will reject both of them. # but feed() will reject both of them.
yield markup, None, None, False yield markup, None, None, False
yield markup, None, None, False
import re import re
self.assertRaisesRegex( self.assertRaisesRegex(
@ -418,13 +417,13 @@ class TestEncodingConversion(SoupTest):
def test_ascii_in_unicode_out(self): def test_ascii_in_unicode_out(self):
# ASCII input is converted to Unicode. The original_encoding # ASCII input is converted to Unicode. The original_encoding
# attribute is set to 'utf-8', a superset of ASCII. # attribute is set to 'utf-8', a superset of ASCII.
chardet = bs4.dammit.chardet_dammit chardet = chardet_dammit
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
try: try:
def noop(str): def noop(str):
return None return None
# Disable chardet, which will realize that the ASCII is ASCII. # Disable chardet, which will realize that the ASCII is ASCII.
bs4.dammit.chardet_dammit = noop chardet_dammit = noop
ascii = b"<foo>a</foo>" ascii = b"<foo>a</foo>"
soup_from_ascii = self.soup(ascii) soup_from_ascii = self.soup(ascii)
unicode_output = soup_from_ascii.decode() unicode_output = soup_from_ascii.decode()
@ -433,7 +432,7 @@ class TestEncodingConversion(SoupTest):
self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8") self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8")
finally: finally:
logging.disable(logging.NOTSET) logging.disable(logging.NOTSET)
bs4.dammit.chardet_dammit = chardet chardet_dammit = chardet
def test_unicode_in_unicode_out(self): def test_unicode_in_unicode_out(self):
# Unicode input is left alone. The original_encoding attribute # Unicode input is left alone. The original_encoding attribute
@ -574,12 +573,12 @@ class TestUnicodeDammit(unittest.TestCase):
doc = b"""\357\273\277<?xml version="1.0" encoding="UTF-8"?> doc = b"""\357\273\277<?xml version="1.0" encoding="UTF-8"?>
<html><b>\330\250\330\252\330\261</b> <html><b>\330\250\330\252\330\261</b>
<i>\310\322\321\220\312\321\355\344</i></html>""" <i>\310\322\321\220\312\321\355\344</i></html>"""
chardet = bs4.dammit.chardet_dammit chardet = chardet_dammit
logging.disable(logging.WARNING) logging.disable(logging.WARNING)
try: try:
def noop(str): def noop(str):
return None return None
bs4.dammit.chardet_dammit = noop chardet_dammit = noop
dammit = UnicodeDammit(doc) dammit = UnicodeDammit(doc)
self.assertEqual(True, dammit.contains_replacement_characters) self.assertEqual(True, dammit.contains_replacement_characters)
self.assertTrue("\ufffd" in dammit.unicode_markup) self.assertTrue("\ufffd" in dammit.unicode_markup)
@ -588,7 +587,7 @@ class TestUnicodeDammit(unittest.TestCase):
self.assertTrue(soup.contains_replacement_characters) self.assertTrue(soup.contains_replacement_characters)
finally: finally:
logging.disable(logging.NOTSET) logging.disable(logging.NOTSET)
bs4.dammit.chardet_dammit = chardet chardet_dammit = chardet
def test_byte_order_mark_removed(self): def test_byte_order_mark_removed(self):
# A document written in UTF-16LE will have its byte order marker stripped. # A document written in UTF-16LE will have its byte order marker stripped.
@ -613,7 +612,7 @@ class TestUnicodeDammit(unittest.TestCase):
self.assertRaises(UnicodeDecodeError, doc.decode, "utf8") self.assertRaises(UnicodeDecodeError, doc.decode, "utf8")
# Unicode, Dammit thinks the whole document is Windows-1252, # Unicode, Dammit thinks the whole document is Windows-1252,
# and decodes it into "☃☃☃Hi, I like Windows!☃☃☃" # and decodes it into "☃☃☃"Hi, I like Windows!"☃☃☃"
# But if we run it through fix_embedded_windows_1252, it's fixed: # But if we run it through fix_embedded_windows_1252, it's fixed:

View File

@ -14,12 +14,12 @@ import copy
import pickle import pickle
import re import re
import warnings import warnings
from bs4 import BeautifulSoup from .. import BeautifulSoup
from bs4.builder import ( from ..builder import (
builder_registry, builder_registry,
HTMLParserTreeBuilder, HTMLParserTreeBuilder,
) )
from bs4.element import ( from ..element import (
PY3K, PY3K,
CData, CData,
Comment, Comment,
@ -33,11 +33,11 @@ from bs4.element import (
Tag, Tag,
TemplateString, TemplateString,
) )
from bs4.testing import ( from ..testing import (
SoupTest, SoupTest,
skipIf, skipIf,
) )
from soupsieve import SelectorSyntaxError from ...soupsieve import SelectorSyntaxError
XML_BUILDER_PRESENT = (builder_registry.lookup("xml") is not None) XML_BUILDER_PRESENT = (builder_registry.lookup("xml") is not None)
LXML_PRESENT = (builder_registry.lookup("lxml") is not None) LXML_PRESENT = (builder_registry.lookup("lxml") is not None)

View File

@ -6,10 +6,13 @@ from .import css_types as ct
import unicodedata import unicodedata
from collections.abc import Sequence from collections.abc import Sequence
import bs4 from ..bs4 import BeautifulSoup
from ..bs4.element import (
Tag, NavigableString, Comment, Declaration, CData, ProcessingInstruction, Doctype
)
# Empty tag pattern (whitespace okay) # Empty tag pattern (whitespace okay)
RE_NOT_EMPTY = re.compile('[^ \t\r\n\f]') RE_NOT_EMPTY = re.compile(r'[^ \t\r\n\f]')
RE_NOT_WS = re.compile('[^ \t\r\n\f]+') RE_NOT_WS = re.compile('[^ \t\r\n\f]+')
@ -90,37 +93,37 @@ class _DocumentNav(object):
@staticmethod @staticmethod
def is_doc(obj): def is_doc(obj):
"""Is `BeautifulSoup` object.""" """Is `BeautifulSoup` object."""
return isinstance(obj, bs4.BeautifulSoup) return isinstance(obj, BeautifulSoup)
@staticmethod @staticmethod
def is_tag(obj): def is_tag(obj):
"""Is tag.""" """Is tag."""
return isinstance(obj, bs4.Tag) return isinstance(obj, Tag)
@staticmethod @staticmethod
def is_declaration(obj): # pragma: no cover def is_declaration(obj): # pragma: no cover
"""Is declaration.""" """Is declaration."""
return isinstance(obj, bs4.Declaration) return isinstance(obj, Declaration)
@staticmethod @staticmethod
def is_cdata(obj): def is_cdata(obj):
"""Is CDATA.""" """Is CDATA."""
return isinstance(obj, bs4.CData) return isinstance(obj, CData)
@staticmethod @staticmethod
def is_processing_instruction(obj): # pragma: no cover def is_processing_instruction(obj): # pragma: no cover
"""Is processing instruction.""" """Is processing instruction."""
return isinstance(obj, bs4.ProcessingInstruction) return isinstance(obj, ProcessingInstruction)
@staticmethod @staticmethod
def is_navigable_string(obj): def is_navigable_string(obj):
"""Is navigable string.""" """Is navigable string."""
return isinstance(obj, bs4.NavigableString) return isinstance(obj, NavigableString)
@staticmethod @staticmethod
def is_special_string(obj): def is_special_string(obj):
"""Is special string.""" """Is special string."""
return isinstance(obj, (bs4.Comment, bs4.Declaration, bs4.CData, bs4.ProcessingInstruction, bs4.Doctype)) return isinstance(obj, (Comment, Declaration, CData, ProcessingInstruction, Doctype))
@classmethod @classmethod
def is_content_string(cls, obj): def is_content_string(cls, obj):

View File

@ -11,18 +11,15 @@ import os.path
import concurrent.futures import concurrent.futures
import urllib.request import urllib.request
import base64 import base64
from .lib.bs4 import BeautifulSoup as bs4
# --- Bundled library imports --- from .lib.bs4.element import Comment
# Explicitly import from the 'lib' directory, now that the package root is in sys.path
from lib import bs4
from lib.markdown2 import Markdown
# --- End bundled library imports ---
from functools import partial from functools import partial
from .lib.markdown2 import Markdown
__all__ = ("markdown2html",) __all__ = ("markdown2html",)
# Use the imported module name
markdowner = Markdown(extras=["fenced-code-blocks", "cuddled-lists"]) markdowner = Markdown(extras=["fenced-code-blocks", "cuddled-lists"])
# FIXME: how do I choose how many workers I want? Does thread pool reuse threads or # FIXME: how do I choose how many workers I want? Does thread pool reuse threads or
@ -37,8 +34,7 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
""" """
html = markdowner.convert(markdown) html = markdowner.convert(markdown)
# Use the imported module name soup = bs4(html, "html.parser")
soup = bs4.BeautifulSoup(html, "html.parser")
for img_element in soup.find_all("img"): for img_element in soup.find_all("img"):
src = img_element["src"] src = img_element["src"]
@ -57,17 +53,16 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
# realpath: simplify that paths so that we don't have duplicated caches # realpath: simplify that paths so that we don't have duplicated caches
path = os.path.realpath(os.path.expanduser(os.path.join(basepath, src))) path = os.path.realpath(os.path.expanduser(os.path.join(basepath, src)))
base64_img, (width, height) = get_base64_image(path, re_render, resources) # Renamed local var to avoid conflict base64, (width, height) = get_base64_image(path, re_render, resources)
img_element["src"] = base64_img img_element["src"] = base64
if width > viewport_width: if width > viewport_width:
img_element["width"] = viewport_width img_element["width"] = viewport_width
img_element["height"] = viewport_width * (height / width) img_element["height"] = viewport_width * (height / width)
# remove comments, because they pollute the console with error messages # remove comments, because they pollute the console with error messages
# Use the imported module name
for comment_element in soup.find_all( for comment_element in soup.find_all(
text=lambda text: isinstance(text, bs4.Comment) text=lambda text: isinstance(text, Comment)
): ):
comment_element.extract() comment_element.extract()
@ -84,15 +79,62 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
.replace(" ", '<i class="space">.</i>') .replace(" ", '<i class="space">.</i>')
.replace("\n", "<br />") .replace("\n", "<br />")
) )
# Use the imported module name
code_element.replace_with(bs4.BeautifulSoup(fixed_pre, "html.parser")) code_element.replace_with(bs4(fixed_pre, "html.parser"))
# FIXME: highlight the code using Sublime's syntax # FIXME: highlight the code using Sublime's syntax
# Apply font scaling via inline styles
if font_scale != 1.0:
BASE_PX_SIZE = 15 # Base font size in pixels
TAG_MULTIPLIERS = {
'p': 1.0,
'li': 1.0,
'h1': 2.0,
'h2': 1.8,
'h3': 1.6,
'h4': 1.4,
'h5': 1.2,
'h6': 1.1,
'blockquote': 1.0,
'td': 1.0,
'th': 1.0,
'dt': 1.0,
'dd': 1.0,
'table': 1.0,
'tr': 1.0,
'ul': 1.0,
'ol': 1.0,
'code': 1.0,
'pre': 1.0,
'a': 1.0,
'strong': 1.0,
'em': 1.0,
's': 1.0,
'sup': 1.0,
'sub': 1.0,
'mark': 1.0,
'small': 1.0,
'big': 1.0,
'kbd': 1.0,
'samp': 1.0,
'var': 1.0,
'cite': 1.0,
'dfn': 1.0,
'abbr': 1.0,
'acronym': 1.0,
}
# Find all tags that we want to scale
for element in soup.find_all(list(TAG_MULTIPLIERS.keys())):
multiplier = TAG_MULTIPLIERS.get(element.name, 1.0)
target_size = round(BASE_PX_SIZE * multiplier * font_scale)
# Simple style setting (overwrites existing inline style if any)
# A more robust solution would parse and merge existing styles
element['style'] = "font-size: {}px;".format(target_size)
# FIXME: report that ST doesn't support <br/> but does work with <br />... WTF? # FIXME: report that ST doesn't support <br/> but does work with <br />... WTF?
# Add font scaling CSS rule stylesheet = resources["stylesheet"] # Use only the base stylesheet
font_scale_css = "body {{ font-size: {}em; }}\n".format(font_scale)
stylesheet = font_scale_css + resources["stylesheet"]
return "<style>\n{}\n</style>\n\n{}".format(stylesheet, soup).replace( return "<style>\n{}\n</style>\n\n{}".format(stylesheet, soup).replace(
"<br/>", "<br />" "<br/>", "<br />"
@ -200,13 +242,19 @@ def get_image_size(fhandle, pathlike):
fhandle.seek(size, 1) fhandle.seek(size, 1)
byte = fhandle.read(1) byte = fhandle.read(1)
if byte == b"": if byte == b"":
fhandle = end # Reached end of file unexpectedly, break the loop
byte = fhandle.read(1) break
while ord(byte) == 0xFF: while ord(byte) == 0xFF:
byte = fhandle.read(1) byte = fhandle.read(1)
if byte == b"": # Check EOF in inner loop too
break
if byte == b"": # Break outer loop if inner loop hit EOF
break
ftype = ord(byte) ftype = ord(byte)
size = struct.unpack(">H", fhandle.read(2))[0] - 2 size = struct.unpack(">H", fhandle.read(2))[0] - 2
# Check if the loop exited because of a break (EOF) before finding the marker
if not (0xC0 <= ftype <= 0xCF):
return "unknown format {!r}".format(format_)
# We are at a SOFn block # We are at a SOFn block
fhandle.seek(1, 1) # Skip `precision' byte. fhandle.seek(1, 1) # Skip `precision' byte.
height, width = struct.unpack(">HH", fhandle.read(4)) height, width = struct.unpack(">HH", fhandle.read(4))

View File

@ -2,7 +2,7 @@
"schema_version": "3.0.0", "schema_version": "3.0.0",
"packages": [ "packages": [
{ {
"name": "MarkdownLivePreview", "name": "MarkdownLivePreview-FORK",
"description": "My enhanced live-preview fork of MarkdownLivePreview", "description": "My enhanced live-preview fork of MarkdownLivePreview",
"author": "Christian Morpurgo", "author": "Christian Morpurgo",
"homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview", "homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview",

40
test_imports.py Normal file
View File

@ -0,0 +1,40 @@
# test_imports.py
import sys
import os
# Optional: Explicitly add project root to path if needed,
# although running from the root often suffices.
# project_root = os.path.dirname(__file__)
# if project_root not in sys.path:
# sys.path.insert(0, project_root)
print("Attempting imports...")
try:
# Try importing the main entry point for bs4 from the lib structure
from lib.bs4 import BeautifulSoup
print("- Successfully imported BeautifulSoup from lib.bs4")
# Try creating a simple soup object (tests basic bs4 internal imports)
soup = BeautifulSoup("<a></a>", "html.parser")
print(f"- Created soup object: {soup.a}")
# Try importing the main entry point for soupsieve
from lib.soupsieve import compile as soupsieve_compile
print("- Successfully imported compile from lib.soupsieve")
# Try compiling a simple selector (tests basic soupsieve internal imports)
compiled = soupsieve_compile("a")
print(f"- Compiled selector: {compiled.pattern}")
# Try using the selector (tests soupsieve -> bs4 interaction)
match = compiled.select_one(soup)
print(f"- Selector match: {match}")
print("\nBasic import and usage tests passed!")
except ImportError as e:
print(f"\nImport Error: {e}")
print("Failed to import. Check paths and internal library structure.")
except Exception as e:
print(f"\nRuntime Error: {e}")
print("Imports might have worked, but usage failed.")