Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f4e6cd4ab0 | |||
| c90d3071ea | |||
| 9de735aace | |||
| 786a72126c | |||
| 70022a9b6c | |||
| 1f7b68e432 | |||
| 507a7e5d92 | |||
| cc5d737c16 | |||
| a7116d7206 | |||
| 4563a0f653 | |||
| e7a15ea070 | |||
| 34f8b3d733 | |||
| e6a880d2a4 | |||
| 9cb5e2087f | |||
| 20dc7da4e8 | |||
| 0413ecca4b | |||
| 37660abe64 | |||
| c2093e19aa | |||
| b9ee2819e3 | |||
| 99a4f21be3 | |||
| edb424de99 | |||
| d89e34f3e2 | |||
| ecfd19dd85 | |||
| 69c722a933 | |||
| 7c37a9e413 | |||
| 89d712fcce | |||
| 9f243f09c9 | |||
| ce768dce10 | |||
| 731f2def96 | |||
| b3ffe8bf55 | |||
| 8576d4a631 | |||
| 49329d1f64 | |||
| 21b67f5b86 | |||
| 837979232e | |||
| 547225ac4d | |||
| e05516ab22 | |||
| bbb90a8a97 | |||
| 598e22002b | |||
| e81b359294 | |||
| 8709e88fbd | |||
| b94ad5856d | |||
| 3004ab4b41 | |||
| 14f6474cd5 | |||
| d0323405c0 | |||
| 661f5b8911 | |||
| 3947b4cc4d | |||
| 2b35cf5000 | |||
| 72c684e89c | |||
| ae1ea101d2 | |||
| 3dd7b5a18d | |||
| aefb27614f | |||
| ed336866ee | |||
| afa554b52b | |||
| e0daa147f1 | |||
| f3cd6d3fcf | |||
| 0034602fff | |||
| b29d054de2 |
2
.gitattributes
vendored
2
.gitattributes
vendored
@ -1,3 +1,5 @@
|
||||
docs/ export-ignore
|
||||
resources/
|
||||
!resources/*.base64
|
||||
test_imports.py export-ignore
|
||||
MarkdownLivePreview.sublime-package export-ignore
|
||||
@ -1,8 +1,21 @@
|
||||
import os, sys, sublime
|
||||
pkg = os.path.basename(os.path.dirname(__file__))
|
||||
lib = os.path.join(sublime.packages_path(), pkg, "lib")
|
||||
if lib not in sys.path:
|
||||
sys.path.insert(0, lib)
|
||||
import os
|
||||
import sys
|
||||
import sublime
|
||||
|
||||
# Add the package archive path itself to sys.path
|
||||
package_path = os.path.dirname(__file__)
|
||||
if package_path not in sys.path:
|
||||
sys.path.insert(0, package_path)
|
||||
|
||||
# --- Add lib to sys.path ---
|
||||
# Get the directory containing this file (MarkdownLivePreview.py)
|
||||
plugin_dir = os.path.dirname(__file__)
|
||||
# Construct the absolute path to the 'lib' directory
|
||||
lib_path = os.path.join(plugin_dir, 'lib')
|
||||
# Add it to the beginning of sys.path if it's not already there
|
||||
if lib_path not in sys.path:
|
||||
sys.path.insert(0, lib_path)
|
||||
# --- End sys.path modification ---
|
||||
|
||||
"""
|
||||
Terminology
|
||||
@ -38,7 +51,7 @@ def plugin_loaded():
|
||||
resources["stylesheet"] = get_resource("stylesheet.css")
|
||||
# FIXME: how could we make this setting update without restarting sublime text
|
||||
# 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):
|
||||
@ -49,6 +62,7 @@ class MdlpInsertCommand(sublime_plugin.TextCommand):
|
||||
class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
|
||||
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
|
||||
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 """
|
||||
@ -56,22 +70,30 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
|
||||
original_view = self.view
|
||||
original_window_id = original_view.window().id()
|
||||
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")
|
||||
|
||||
if file_name:
|
||||
original_view.close()
|
||||
else:
|
||||
# the file isn't saved, we need to restore the content manually
|
||||
# don't close the original view; keep it in your main window:
|
||||
# if file_name:
|
||||
# original_view.close()
|
||||
# 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())
|
||||
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
|
||||
print("--- MarkdownLivePreview: Unsaved file, content length: {} ---".format(len(content)))
|
||||
|
||||
sublime.run_command("new_window")
|
||||
preview_window = sublime.active_window()
|
||||
# instead of making a new window, grab your existing one:
|
||||
preview_window = original_view.window()
|
||||
print("--- MarkdownLivePreview: Using existing window ID: {} ---".format(preview_window.id()))
|
||||
|
||||
preview_window.run_command(
|
||||
"set_layout",
|
||||
@ -87,20 +109,44 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
|
||||
preview_view.set_scratch(True)
|
||||
preview_view.settings().set(PREVIEW_VIEW_INFOS, {})
|
||||
preview_view.set_name("Preview")
|
||||
print("--- MarkdownLivePreview: Created preview_view ID: {} ---".format(preview_view.id()))
|
||||
# FIXME: hide number lines on preview
|
||||
|
||||
preview_window.focus_group(0)
|
||||
if 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:
|
||||
markdown_view = preview_window.new_file()
|
||||
markdown_view.run_command("mdlp_insert", {"point": 0, "string": content})
|
||||
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.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):
|
||||
# FIXME: is this the best way there is to check if the current syntax is markdown?
|
||||
@ -111,6 +157,7 @@ class OpenMarkdownPreviewCommand(sublime_plugin.TextCommand):
|
||||
|
||||
|
||||
class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
instance = None # Class variable to hold the single instance
|
||||
|
||||
phantom_sets = {
|
||||
# markdown_view.id(): phantom set
|
||||
@ -120,6 +167,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
# then, we update only if now() - last_update > DELAY
|
||||
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...
|
||||
|
||||
def on_pre_close(self, markdown_view):
|
||||
@ -136,18 +188,30 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
if self.file_name is None:
|
||||
total_region = sublime.Region(0, markdown_view.size())
|
||||
self.content = markdown_view.substr(total_region)
|
||||
markdown_view.erase(edit, total_region)
|
||||
else:
|
||||
self.content = None
|
||||
|
||||
def on_load_async(self, markdown_view):
|
||||
infos = markdown_view.settings().get(MARKDOWN_VIEW_INFOS)
|
||||
if not infos:
|
||||
# print("--- MarkdownLivePreview: on_load_async ignored for view {} - no infos ---".format(markdown_view.id())) # Optional: very verbose
|
||||
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)
|
||||
|
||||
def on_close(self, markdown_view):
|
||||
@ -157,13 +221,11 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
if not infos:
|
||||
return
|
||||
|
||||
assert (
|
||||
markdown_view.id() == self.markdown_view.id()
|
||||
), "pre_close view.id() != close view.id()"
|
||||
if markdown_view.id() in self.phantom_sets:
|
||||
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
|
||||
original_window = next(
|
||||
@ -194,14 +256,17 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
# @min_time_between_call(.5)
|
||||
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)
|
||||
if not infos:
|
||||
return
|
||||
|
||||
print("--- MarkdownLivePreview: Scheduling update for markdown_view {} ---".format(markdown_view.id()))
|
||||
# we schedule an update, which won't run if an
|
||||
sublime.set_timeout(partial(self._update_preview, markdown_view), DELAY)
|
||||
|
||||
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
|
||||
# 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
|
||||
@ -210,25 +275,53 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
settings = get_settings()
|
||||
delay = settings.get(SETTING_DELAY_BETWEEN_UPDATES, 100) # 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:
|
||||
print("--- MarkdownLivePreview: Update skipped for view {} due to time delay ---".format(markdown_view.id()))
|
||||
return
|
||||
|
||||
if markdown_view.buffer_id() == 0:
|
||||
print("--- MarkdownLivePreview: Update skipped for view {}: buffer_id is 0 (view closed) ---".format(markdown_view.id()))
|
||||
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()
|
||||
|
||||
total_region = sublime.Region(0, markdown_view.size())
|
||||
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
|
||||
viewport_extent = preview_view.viewport_extent()
|
||||
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
|
||||
print("--- MarkdownLivePreview: Calling markdown2html with basepath: {} ---".format(basepath))
|
||||
html = markdown2html(
|
||||
markdown,
|
||||
basepath,
|
||||
@ -238,6 +331,10 @@ class MarkdownLivePreviewListener(sublime_plugin.EventListener):
|
||||
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(
|
||||
[
|
||||
sublime.Phantom(
|
||||
@ -248,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():
|
||||
@ -255,11 +378,15 @@ def get_settings():
|
||||
|
||||
|
||||
def get_resource(resource):
|
||||
path = "Packages/MarkdownLivePreview/resources/" + resource
|
||||
abs_path = os.path.join(sublime.packages_path(), "..", path)
|
||||
package_name = __package__ # Get the current package name dynamically
|
||||
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):
|
||||
with open(abs_path, "r") as fp:
|
||||
return fp.read()
|
||||
# Fallback to sublime.load_resource (works for packed and unpacked)
|
||||
return sublime.load_resource(path)
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
22
README.md
22
README.md
@ -1,5 +1,25 @@
|
||||
# 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.
|
||||
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 :-)
|
||||
|
||||
```
|
||||
$ git clone https://github.com/math2001/MarkdownLivePreview
|
||||
$ git clone https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview
|
||||
$ cd MarkdownLivePreview
|
||||
$ grep -R FIXME
|
||||
```
|
||||
|
||||
6
create_package.sh
Executable file
6
create_package.sh
Executable 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
|
||||
@ -4,7 +4,7 @@ __license__ = "MIT"
|
||||
from collections import defaultdict
|
||||
import itertools
|
||||
import sys
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
CharsetMetaAttributeValue,
|
||||
ContentMetaAttributeValue,
|
||||
Stylesheet,
|
||||
|
||||
@ -7,13 +7,13 @@ __all__ = [
|
||||
|
||||
import warnings
|
||||
import re
|
||||
from bs4.builder import (
|
||||
from . import (
|
||||
PERMISSIVE,
|
||||
HTML,
|
||||
HTML_5,
|
||||
HTMLTreeBuilder,
|
||||
)
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
NamespacedAttribute,
|
||||
nonwhitespace_re,
|
||||
)
|
||||
@ -22,7 +22,7 @@ from html5lib.constants import (
|
||||
namespaces,
|
||||
prefixes,
|
||||
)
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
Comment,
|
||||
Doctype,
|
||||
NavigableString,
|
||||
@ -120,7 +120,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
|
||||
if soup:
|
||||
self.soup = soup
|
||||
else:
|
||||
from bs4 import BeautifulSoup
|
||||
from .. import BeautifulSoup
|
||||
# TODO: Why is the parser 'html.parser' here? To avoid an
|
||||
# infinite loop?
|
||||
self.soup = BeautifulSoup(
|
||||
@ -166,7 +166,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
|
||||
return TextNode(Comment(data), self.soup)
|
||||
|
||||
def fragmentClass(self):
|
||||
from bs4 import BeautifulSoup
|
||||
from .. import BeautifulSoup
|
||||
# TODO: Why is the parser 'html.parser' here? To avoid an
|
||||
# infinite loop?
|
||||
self.soup = BeautifulSoup("", "html.parser")
|
||||
@ -184,7 +184,7 @@ class TreeBuilderForHtml5lib(treebuilder_base.TreeBuilder):
|
||||
return treebuilder_base.TreeBuilder.getFragment(self).element
|
||||
|
||||
def testSerializer(self, element):
|
||||
from bs4 import BeautifulSoup
|
||||
from .. import BeautifulSoup
|
||||
rv = []
|
||||
doctype_re = re.compile(r'^(.*?)(?: PUBLIC "(.*?)"(?: "(.*?)")?| SYSTEM "(.*?)")?$')
|
||||
|
||||
|
||||
@ -34,16 +34,16 @@ CONSTRUCTOR_STRICT_IS_DEPRECATED = major == 3 and minor == 3
|
||||
CONSTRUCTOR_TAKES_CONVERT_CHARREFS = major == 3 and minor >= 4
|
||||
|
||||
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
CData,
|
||||
Comment,
|
||||
Declaration,
|
||||
Doctype,
|
||||
ProcessingInstruction,
|
||||
)
|
||||
from bs4.dammit import EntitySubstitution, UnicodeDammit
|
||||
from ..dammit import EntitySubstitution, UnicodeDammit
|
||||
|
||||
from bs4.builder import (
|
||||
from . import (
|
||||
HTML,
|
||||
HTMLTreeBuilder,
|
||||
STRICT,
|
||||
|
||||
@ -14,14 +14,14 @@ except ImportError as e:
|
||||
from io import BytesIO
|
||||
from io import StringIO
|
||||
from lxml import etree
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
Comment,
|
||||
Doctype,
|
||||
NamespacedAttribute,
|
||||
ProcessingInstruction,
|
||||
XMLProcessingInstruction,
|
||||
)
|
||||
from bs4.builder import (
|
||||
from . import (
|
||||
FAST,
|
||||
HTML,
|
||||
HTMLTreeBuilder,
|
||||
@ -29,7 +29,7 @@ from bs4.builder import (
|
||||
ParserRejectedMarkup,
|
||||
TreeBuilder,
|
||||
XML)
|
||||
from bs4.dammit import EncodingDetector
|
||||
from ..dammit import EncodingDetector
|
||||
|
||||
LXML = 'lxml'
|
||||
|
||||
|
||||
@ -6,9 +6,9 @@ __license__ = "MIT"
|
||||
import cProfile
|
||||
from io import StringIO
|
||||
from html.parser import HTMLParser
|
||||
import bs4
|
||||
from bs4 import BeautifulSoup, __version__
|
||||
from bs4.builder import builder_registry
|
||||
from . import BeautifulSoup as bs4
|
||||
from . import BeautifulSoup, __version__
|
||||
from .builder import builder_registry
|
||||
|
||||
import os
|
||||
import pstats
|
||||
|
||||
@ -9,14 +9,16 @@ import re
|
||||
import sys
|
||||
import warnings
|
||||
try:
|
||||
import soupsieve
|
||||
# We are installed under the bs4 package
|
||||
from soupsieve import *
|
||||
except ImportError as e:
|
||||
# We are installed standalone, or soupsieve is not installed.
|
||||
soupsieve = None
|
||||
warnings.warn(
|
||||
'The soupsieve package is not installed. CSS selectors cannot be used.'
|
||||
)
|
||||
|
||||
from bs4.formatter import (
|
||||
from .formatter import (
|
||||
Formatter,
|
||||
HTMLFormatter,
|
||||
XMLFormatter,
|
||||
@ -380,7 +382,7 @@ class PageElement(object):
|
||||
and not isinstance(new_child, NavigableString)):
|
||||
new_child = NavigableString(new_child)
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from . import BeautifulSoup
|
||||
if isinstance(new_child, BeautifulSoup):
|
||||
# We don't want to end up with a situation where one BeautifulSoup
|
||||
# object contains another. Insert the children one at a time.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
from bs4.dammit import EntitySubstitution
|
||||
from .dammit import EntitySubstitution
|
||||
|
||||
class Formatter(EntitySubstitution):
|
||||
"""Describes a strategy to use when outputting a parse tree to a string.
|
||||
|
||||
@ -9,8 +9,8 @@ import copy
|
||||
import functools
|
||||
import unittest
|
||||
from unittest import TestCase
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.element import (
|
||||
from . import BeautifulSoup
|
||||
from .element import (
|
||||
CharsetMetaAttributeValue,
|
||||
Comment,
|
||||
ContentMetaAttributeValue,
|
||||
@ -22,7 +22,7 @@ from bs4.element import (
|
||||
Tag
|
||||
)
|
||||
|
||||
from bs4.builder import HTMLParserTreeBuilder
|
||||
from .builder import HTMLParserTreeBuilder
|
||||
default_builder = HTMLParserTreeBuilder
|
||||
|
||||
BAD_DOCUMENT = """A bare string
|
||||
|
||||
@ -3,21 +3,21 @@
|
||||
import unittest
|
||||
import warnings
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.builder import (
|
||||
from .. import BeautifulSoup
|
||||
from ..builder import (
|
||||
builder_registry as registry,
|
||||
HTMLParserTreeBuilder,
|
||||
TreeBuilderRegistry,
|
||||
)
|
||||
|
||||
try:
|
||||
from bs4.builder import HTML5TreeBuilder
|
||||
from ..builder import HTML5TreeBuilder
|
||||
HTML5LIB_PRESENT = True
|
||||
except ImportError:
|
||||
HTML5LIB_PRESENT = False
|
||||
|
||||
try:
|
||||
from bs4.builder import (
|
||||
from ..builder import (
|
||||
LXMLTreeBuilderForXML,
|
||||
LXMLTreeBuilder,
|
||||
)
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from bs4.builder import HTML5TreeBuilder
|
||||
from ..builder import HTML5TreeBuilder
|
||||
HTML5LIB_PRESENT = True
|
||||
except ImportError as e:
|
||||
HTML5LIB_PRESENT = False
|
||||
from bs4.element import SoupStrainer
|
||||
from bs4.testing import (
|
||||
from ..element import SoupStrainer
|
||||
from ..testing import (
|
||||
HTML5TreeBuilderSmokeTest,
|
||||
SoupTest,
|
||||
skipIf,
|
||||
|
||||
@ -3,9 +3,9 @@ trees."""
|
||||
|
||||
from pdb import set_trace
|
||||
import pickle
|
||||
from bs4.testing import SoupTest, HTMLTreeBuilderSmokeTest
|
||||
from bs4.builder import HTMLParserTreeBuilder
|
||||
from bs4.builder._htmlparser import BeautifulSoupHTMLParser
|
||||
from ..testing import SoupTest, HTMLTreeBuilderSmokeTest
|
||||
from ..builder import HTMLParserTreeBuilder
|
||||
from ..builder._htmlparser import BeautifulSoupHTMLParser
|
||||
|
||||
class HTMLParserTreeBuilderSmokeTest(SoupTest, HTMLTreeBuilderSmokeTest):
|
||||
|
||||
|
||||
@ -12,16 +12,16 @@ except ImportError as e:
|
||||
LXML_VERSION = (0,)
|
||||
|
||||
if LXML_PRESENT:
|
||||
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
|
||||
from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
|
||||
|
||||
from bs4 import (
|
||||
from .. import (
|
||||
BeautifulSoup,
|
||||
BeautifulStoneSoup,
|
||||
)
|
||||
from bs4.element import Comment, Doctype, SoupStrainer
|
||||
from bs4.testing import skipIf
|
||||
from bs4.tests import test_htmlparser
|
||||
from bs4.testing import (
|
||||
from ..element import Comment, Doctype, SoupStrainer
|
||||
from ..testing import skipIf
|
||||
from . import test_htmlparser
|
||||
from ..testing import (
|
||||
HTMLTreeBuilderSmokeTest,
|
||||
XMLTreeBuilderSmokeTest,
|
||||
SoupTest,
|
||||
|
||||
@ -7,17 +7,17 @@ import unittest
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from bs4 import (
|
||||
from .. import (
|
||||
BeautifulSoup,
|
||||
BeautifulStoneSoup,
|
||||
GuessedAtParserWarning,
|
||||
MarkupResemblesLocatorWarning,
|
||||
)
|
||||
from bs4.builder import (
|
||||
from ..builder import (
|
||||
TreeBuilder,
|
||||
ParserRejectedMarkup,
|
||||
)
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
CharsetMetaAttributeValue,
|
||||
Comment,
|
||||
ContentMetaAttributeValue,
|
||||
@ -27,13 +27,13 @@ from bs4.element import (
|
||||
NavigableString,
|
||||
)
|
||||
|
||||
import bs4.dammit
|
||||
from bs4.dammit import (
|
||||
from ..dammit import *
|
||||
from ..dammit import (
|
||||
EntitySubstitution,
|
||||
UnicodeDammit,
|
||||
EncodingDetector,
|
||||
)
|
||||
from bs4.testing import (
|
||||
from ..testing import (
|
||||
default_builder,
|
||||
SoupTest,
|
||||
skipIf,
|
||||
@ -41,7 +41,7 @@ from bs4.testing import (
|
||||
import warnings
|
||||
|
||||
try:
|
||||
from bs4.builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
|
||||
from ..builder import LXMLTreeBuilder, LXMLTreeBuilderForXML
|
||||
LXML_PRESENT = True
|
||||
except ImportError as e:
|
||||
LXML_PRESENT = False
|
||||
@ -120,11 +120,10 @@ class TestConstructor(SoupTest):
|
||||
def feed(self, *args, **kwargs):
|
||||
raise ParserRejectedMarkup("Nope.")
|
||||
|
||||
def prepare_markup(self, *args, **kwargs):
|
||||
# We're going to try two different ways of preparing this markup,
|
||||
# but feed() will reject both of them.
|
||||
yield markup, None, None, False
|
||||
yield markup, None, None, False
|
||||
def prepare_markup(self, markup, *args, **kwargs):
|
||||
# We're going to try two different ways of preparing this markup,
|
||||
# but feed() will reject both of them.
|
||||
yield markup, None, None, False
|
||||
|
||||
import re
|
||||
self.assertRaisesRegex(
|
||||
@ -418,13 +417,13 @@ class TestEncodingConversion(SoupTest):
|
||||
def test_ascii_in_unicode_out(self):
|
||||
# ASCII input is converted to Unicode. The original_encoding
|
||||
# attribute is set to 'utf-8', a superset of ASCII.
|
||||
chardet = bs4.dammit.chardet_dammit
|
||||
chardet = chardet_dammit
|
||||
logging.disable(logging.WARNING)
|
||||
try:
|
||||
def noop(str):
|
||||
return None
|
||||
# Disable chardet, which will realize that the ASCII is ASCII.
|
||||
bs4.dammit.chardet_dammit = noop
|
||||
chardet_dammit = noop
|
||||
ascii = b"<foo>a</foo>"
|
||||
soup_from_ascii = self.soup(ascii)
|
||||
unicode_output = soup_from_ascii.decode()
|
||||
@ -433,7 +432,7 @@ class TestEncodingConversion(SoupTest):
|
||||
self.assertEqual(soup_from_ascii.original_encoding.lower(), "utf-8")
|
||||
finally:
|
||||
logging.disable(logging.NOTSET)
|
||||
bs4.dammit.chardet_dammit = chardet
|
||||
chardet_dammit = chardet
|
||||
|
||||
def test_unicode_in_unicode_out(self):
|
||||
# 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"?>
|
||||
<html><b>\330\250\330\252\330\261</b>
|
||||
<i>\310\322\321\220\312\321\355\344</i></html>"""
|
||||
chardet = bs4.dammit.chardet_dammit
|
||||
chardet = chardet_dammit
|
||||
logging.disable(logging.WARNING)
|
||||
try:
|
||||
def noop(str):
|
||||
return None
|
||||
bs4.dammit.chardet_dammit = noop
|
||||
chardet_dammit = noop
|
||||
dammit = UnicodeDammit(doc)
|
||||
self.assertEqual(True, dammit.contains_replacement_characters)
|
||||
self.assertTrue("\ufffd" in dammit.unicode_markup)
|
||||
@ -588,7 +587,7 @@ class TestUnicodeDammit(unittest.TestCase):
|
||||
self.assertTrue(soup.contains_replacement_characters)
|
||||
finally:
|
||||
logging.disable(logging.NOTSET)
|
||||
bs4.dammit.chardet_dammit = chardet
|
||||
chardet_dammit = chardet
|
||||
|
||||
def test_byte_order_mark_removed(self):
|
||||
# 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")
|
||||
|
||||
# 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:
|
||||
|
||||
|
||||
@ -14,12 +14,12 @@ import copy
|
||||
import pickle
|
||||
import re
|
||||
import warnings
|
||||
from bs4 import BeautifulSoup
|
||||
from bs4.builder import (
|
||||
from .. import BeautifulSoup
|
||||
from ..builder import (
|
||||
builder_registry,
|
||||
HTMLParserTreeBuilder,
|
||||
)
|
||||
from bs4.element import (
|
||||
from ..element import (
|
||||
PY3K,
|
||||
CData,
|
||||
Comment,
|
||||
@ -33,11 +33,11 @@ from bs4.element import (
|
||||
Tag,
|
||||
TemplateString,
|
||||
)
|
||||
from bs4.testing import (
|
||||
from ..testing import (
|
||||
SoupTest,
|
||||
skipIf,
|
||||
)
|
||||
from soupsieve import SelectorSyntaxError
|
||||
from ...soupsieve import SelectorSyntaxError
|
||||
|
||||
XML_BUILDER_PRESENT = (builder_registry.lookup("xml") is not None)
|
||||
LXML_PRESENT = (builder_registry.lookup("lxml") is not None)
|
||||
|
||||
@ -6,10 +6,13 @@ from .import css_types as ct
|
||||
import unicodedata
|
||||
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)
|
||||
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]+')
|
||||
|
||||
@ -90,37 +93,37 @@ class _DocumentNav(object):
|
||||
@staticmethod
|
||||
def is_doc(obj):
|
||||
"""Is `BeautifulSoup` object."""
|
||||
return isinstance(obj, bs4.BeautifulSoup)
|
||||
return isinstance(obj, BeautifulSoup)
|
||||
|
||||
@staticmethod
|
||||
def is_tag(obj):
|
||||
"""Is tag."""
|
||||
return isinstance(obj, bs4.Tag)
|
||||
return isinstance(obj, Tag)
|
||||
|
||||
@staticmethod
|
||||
def is_declaration(obj): # pragma: no cover
|
||||
"""Is declaration."""
|
||||
return isinstance(obj, bs4.Declaration)
|
||||
return isinstance(obj, Declaration)
|
||||
|
||||
@staticmethod
|
||||
def is_cdata(obj):
|
||||
"""Is CDATA."""
|
||||
return isinstance(obj, bs4.CData)
|
||||
return isinstance(obj, CData)
|
||||
|
||||
@staticmethod
|
||||
def is_processing_instruction(obj): # pragma: no cover
|
||||
"""Is processing instruction."""
|
||||
return isinstance(obj, bs4.ProcessingInstruction)
|
||||
return isinstance(obj, ProcessingInstruction)
|
||||
|
||||
@staticmethod
|
||||
def is_navigable_string(obj):
|
||||
"""Is navigable string."""
|
||||
return isinstance(obj, bs4.NavigableString)
|
||||
return isinstance(obj, NavigableString)
|
||||
|
||||
@staticmethod
|
||||
def is_special_string(obj):
|
||||
"""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
|
||||
def is_content_string(cls, obj):
|
||||
|
||||
@ -11,11 +11,12 @@ import os.path
|
||||
import concurrent.futures
|
||||
import urllib.request
|
||||
import base64
|
||||
import bs4
|
||||
from .lib.bs4 import BeautifulSoup as bs4
|
||||
from .lib.bs4.element import Comment
|
||||
|
||||
from functools import partial
|
||||
|
||||
from markdown2 import Markdown
|
||||
from .lib.markdown2 import Markdown
|
||||
|
||||
__all__ = ("markdown2html",)
|
||||
|
||||
@ -33,7 +34,7 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
|
||||
"""
|
||||
html = markdowner.convert(markdown)
|
||||
|
||||
soup = bs4.BeautifulSoup(html, "html.parser")
|
||||
soup = bs4(html, "html.parser")
|
||||
for img_element in soup.find_all("img"):
|
||||
src = img_element["src"]
|
||||
|
||||
@ -61,7 +62,7 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
|
||||
|
||||
# remove comments, because they pollute the console with error messages
|
||||
for comment_element in soup.find_all(
|
||||
text=lambda text: isinstance(text, bs4.Comment)
|
||||
text=lambda text: isinstance(text, Comment)
|
||||
):
|
||||
comment_element.extract()
|
||||
|
||||
@ -79,14 +80,61 @@ def markdown2html(markdown, basepath, re_render, resources, viewport_width, font
|
||||
.replace("\n", "<br />")
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# 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?
|
||||
# Add font scaling CSS rule
|
||||
font_scale_css = "body {{ font-size: {}em; }}\n".format(font_scale)
|
||||
stylesheet = font_scale_css + resources["stylesheet"]
|
||||
stylesheet = resources["stylesheet"] # Use only the base stylesheet
|
||||
|
||||
return "<style>\n{}\n</style>\n\n{}".format(stylesheet, soup).replace(
|
||||
"<br/>", "<br />"
|
||||
@ -194,13 +242,19 @@ def get_image_size(fhandle, pathlike):
|
||||
fhandle.seek(size, 1)
|
||||
byte = fhandle.read(1)
|
||||
if byte == b"":
|
||||
fhandle = end
|
||||
byte = fhandle.read(1)
|
||||
|
||||
# Reached end of file unexpectedly, break the loop
|
||||
break
|
||||
while ord(byte) == 0xFF:
|
||||
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)
|
||||
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
|
||||
fhandle.seek(1, 1) # Skip `precision' byte.
|
||||
height, width = struct.unpack(">HH", fhandle.read(4))
|
||||
|
||||
@ -2,14 +2,14 @@
|
||||
"schema_version": "3.0.0",
|
||||
"packages": [
|
||||
{
|
||||
"name": "MarkdownLivePreview",
|
||||
"name": "MarkdownLivePreview-FORK",
|
||||
"description": "My enhanced live-preview fork of MarkdownLivePreview",
|
||||
"author": "Christian Morpurgo",
|
||||
"homepage": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview",
|
||||
"releases": [
|
||||
{
|
||||
"version": "6.0.1",
|
||||
"url": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/releases/download/v6.0.1/MarkdownLivePreview.sublime-package",
|
||||
"version": "6.0.2",
|
||||
"url": "https://git.0x42.cloud/christian.morpurgo/MarkdownLivePreview/releases/download/v6.0.2/MarkdownLivePreview.sublime-package",
|
||||
"date": "2025-04-24 00:00:00",
|
||||
"sublime_text": "*"
|
||||
}
|
||||
|
||||
40
test_imports.py
Normal file
40
test_imports.py
Normal 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.")
|
||||
Reference in New Issue
Block a user