Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 44 additions & 98 deletions build.sh
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

# Define color codes for output formatting
# ─── Configurable Python binary ─────────────────────────────────────────────
VENV_PYTHON="${VENV_PYTHON:-/home/linux/lightweight-charts-python/venv/bin/python}"

# ─── Color codes ─────────────────────────────────────────────────────────────
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
NC='\033[0m' # No Color

# Define message prefixes
# ─── Message prefixes ───────────────────────────────────────────────────────
ERROR="${RED}[ERROR]${NC} "
INFO="${CYAN}[INFO]${NC} "
WARNING="${YELLOW}[WARNING]${NC} "

# Initialize flags
# ─── Flags ───────────────────────────────────────────────────────────────────
BUILD=false
PACKAGE=false
UPLOAD=false

# Parse command-line options
# ─── Parse command-line options ─────────────────────────────────────────────
while getopts ":bpu" opt; do
case ${opt} in
case "${opt}" in
b )
BUILD=true
;;
Expand All @@ -28,6 +33,8 @@ while getopts ":bpu" opt; do
;;
u )
UPLOAD=true
PACKAGE=true # ensure package runs before upload
BUILD=true # ensure build runs before package
;;
\? )
echo -e "${ERROR}Invalid option: -$OPTARG" >&2
Expand All @@ -36,132 +43,71 @@ while getopts ":bpu" opt; do
esac
done

# If no options are provided, default to build only
if [ "$OPTIND" -eq 1 ]; then
# ─── Default to build if no flags provided ──────────────────────────────────
if ! $BUILD && ! $PACKAGE && ! $UPLOAD; then
BUILD=true
# PACKAGE remains false (we'll prompt), UPLOAD remains false
fi

# Function to perform the build process
# ─── Build process ──────────────────────────────────────────────────────────
perform_build() {
echo -e "${INFO}Starting build process..."

rm -rf dist/bundle.js dist/typings/
if [[ $? -eq 0 ]]; then
echo -e "${INFO}Deleted old build artifacts."
else
echo -e "${WARNING}Could not delete old dist files, continuing..."
fi

npx rollup -c rollup.config.js
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Rollup build failed."
exit 1
fi

cp dist/bundle.js src/general/styles.css lightweight_charts_esistjosh/js
if [[ $? -eq 0 ]]; then
echo -e "${INFO}Copied bundle.js and styles.css into Python package."
else
echo -e "${ERROR}Failed to copy build outputs into Python package."
exit 1
fi

rm -rf dist/bundle.js dist/typings/ || echo -e "${WARNING}No old artifacts to clean"
npx rollup -c rollup.config.js || { echo -e "${ERROR}Rollup build failed."; exit 1; }
cp dist/bundle.js src/general/styles.css lightweight_charts_esistjosh/js/ \
|| { echo -e "${ERROR}Failed to copy build outputs."; exit 1; }
echo -e "${GREEN}[BUILD SUCCESS]${NC}"
}

# Function to perform packaging and installation
# ─── Package & install ───────────────────────────────────────────────────────
perform_package() {
echo -e "${INFO}Starting packaging and installation..."

VENV_PYTHON="/home/linux/lightweight-charts-python/venv/bin/python"

sudo "$VENV_PYTHON" -m build --sdist
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to build source distribution."
exit 1
fi

sudo "$VENV_PYTHON" -m build --wheel
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to build wheel distribution."
exit 1
fi

sudo "$VENV_PYTHON" -m pip install .
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to install the package."
exit 1
fi

echo -e "${INFO}Starting packaging & installation..."
sudo "$VENV_PYTHON" -m build --sdist --wheel \
|| { echo -e "${ERROR}Packaging failed."; exit 1; }
sudo "$VENV_PYTHON" -m pip install . \
|| { echo -e "${ERROR}Installation failed."; exit 1; }
echo -e "${GREEN}[PACKAGE & INSTALL SUCCESS]${NC}"
}

# Function to perform upload
# ─── Upload to PyPI ──────────────────────────────────────────────────────────
perform_upload() {
echo -e "${INFO}Starting upload process..."

OUTPUT_DIR="./output"
if [ -d "$OUTPUT_DIR" ]; then
echo -e "${INFO}Removing existing output directory..."
sudo rm -rf "$OUTPUT_DIR"
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to remove existing output directory."
exit 1
fi
fi

rm -rf "$OUTPUT_DIR"
mkdir -p "$OUTPUT_DIR"
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to create output directory."
exit 1
fi

shopt -s nullglob
files=(./dist/*.tar.gz ./dist/*.whl)
files=(dist/*.tar.gz dist/*.whl)
shopt -u nullglob

if [ ${#files[@]} -eq 0 ]; then
echo -e "${WARNING}No distribution files found in ./dist/. Skipping upload."
echo -e "${WARNING}No distribution files found; skipping upload."
return
fi

mv "${files[@]}" "$OUTPUT_DIR"/
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to move distribution files to output directory."
exit 1
fi
mv "${files[@]}" "$OUTPUT_DIR"/ \
|| { echo -e "${ERROR}Failed to move artifacts to ${OUTPUT_DIR}."; exit 1; }

# ensure twine is available
"$VENV_PYTHON" -m pip install --upgrade twine >/dev/null

"$VENV_PYTHON" -m twine upload "$OUTPUT_DIR"/*
if [[ $? -ne 0 ]]; then
echo -e "${ERROR}Failed to upload distributions to PyPI."
echo -e "${INFO}Uploading to PyPI…"
if ! "$VENV_PYTHON" -m twine upload --verbose "$OUTPUT_DIR"/*; then
echo -e "${ERROR}Twine upload failed. Check credentials and network."
exit 1
fi

echo -e "${GREEN}[UPLOAD SUCCESS]${NC}"
}

# Execute build if requested
if [ "$BUILD" = true ]; then
# ─── Execution order ─────────────────────────────────────────────────────────
if $BUILD; then
perform_build
fi

# If -p was given, package automatically; otherwise prompt
if [ "$PACKAGE" = true ]; then
perform_package
else
read -p "Do you want to package and install? (y/n): " answer
case "$answer" in
[Yy]* )
perform_package
;;
* )
echo -e "${INFO}Skipping packaging."
;;
esac
fi
if $PACKAGE; then
perform_package
fi

# Execute upload only if -u was explicitly passed
if [ "$UPLOAD" = true ]; then
if $UPLOAD; then
perform_upload
fi
12 changes: 6 additions & 6 deletions lightweight_charts_esistjosh/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from .util import (
BulkRunScript, Pane, Events, IDGen, as_enum, jbool, js_json, TIME, NUM, FLOAT,
LINE_STYLE, MARKER_POSITION, MARKER_SHAPE, CANDLE_SHAPE, CROSSHAIR_MODE,
PRICE_SCALE_MODE, marker_position, marker_shape, js_data,
PRICE_SCALE_MODE, TOOLBOX_MODE, marker_position, marker_shape, js_data,
)

current_dir = os.path.dirname(os.path.abspath(__file__))
Expand Down Expand Up @@ -1217,10 +1217,10 @@ def _get_time_or_last_bar(self, time: Optional[Any]) -> str:

return self._convert_time(last_bar_time)


class AbstractChart(Candlestick, Pane):
def __init__(self, window: Window, width: float = 1.0, height: float = 1.0,
scale_candles_only: bool = False, toolbox: bool = False,
scale_candles_only: bool = False,
toolbox: Optional[TOOLBOX_MODE] = "disabled",
autosize: bool = True, position: FLOAT = 'left',
defaults: str = '../defaults', scripts: str = '../scripts'):
Pane.__init__(self, window)
Expand All @@ -1239,10 +1239,10 @@ def __init__(self, window: Window, width: float = 1.0, height: float = 1.0,
self.run_script(
f'{self.id} = new Lib.Handler("{self.id}", {width}, {height}, "{position}", {jbool(autosize)})'
)

self.topbar: TopBar = TopBar(self)
if toolbox:
self.toolbox: ToolBox = ToolBox(self)
if toolbox != "disabled":
self.toolbox: ToolBox = ToolBox(self, mode=toolbox)

# Set and initialize defaults directory
self.defaults = defaults or '../defaults'
Expand Down
7 changes: 3 additions & 4 deletions lightweight_charts_esistjosh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@
import typing
import webview
from webview.errors import JavascriptException

from .util import parse_event_message, FLOAT
from .util import parse_event_message, FLOAT, TOOLBOX_MODE
from .abstract import AbstractChart, Window, INDEX
import os
import threading

from typing import Optional

class CallbackAPI:
def __init__(self, emit_queue):
Expand Down Expand Up @@ -161,7 +160,7 @@ def __init__(
on_top: bool = False,
maximize: bool = False,
debug: bool = False,
toolbox: bool = False,
toolbox: str = "disabled",
inner_width: float = 1.0,
inner_height: float = 1.0,
scale_candles_only: bool = False,
Expand Down
4 changes: 2 additions & 2 deletions lightweight_charts_esistjosh/js/bundle.js

Large diffs are not rendered by default.

17 changes: 14 additions & 3 deletions lightweight_charts_esistjosh/toolbox.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import json
from .util import jbool
from typing import Optional
from .util import TOOLBOX_MODE



class ToolBox:
def __init__(self, chart, toggle = True):
def __init__(self, chart, series_name = None, mode: Optional[TOOLBOX_MODE]= "legacy"):
self.run_script = chart.run_script
self.id = chart.id
self._save_under = None
self.drawings = {}
toggle = mode != "legacy"
chart.win.handlers[f'save_drawings{self.id}'] = self._save_drawings
self.run_script(f'{self.id}.createToolBox({jbool(toggle)})')

# quote the key so JS sees a string literal, or `null` if none
# Emit a JS string literal for the key, or null if none.
js_series_arg = f'"{series_name}"' if series_name else "null"
# Call createToolBox(seriesKey, toggle)
self.run_script(
f"{self.id}.createToolBox({js_series_arg}, {jbool(toggle)})"
)
def save_drawings_under(self, widget: 'Widget'):
"""
Drawings made on charts will be saved under the widget given. eg `chart.toolbox.save_drawings_under(chart.topbar['symbol'])`.
Expand Down Expand Up @@ -43,3 +53,4 @@ def _save_drawings(self, drawings):
if not self._save_under:
return
self.drawings[self._save_under.value] = json.loads(drawings)

21 changes: 19 additions & 2 deletions lightweight_charts_esistjosh/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,25 @@ def generate(self) -> str:
self.generate()



def parse_event_message(window, string):
"""
Parses messages of the form "handlerName_~_arg1;;;arg2;;;…"
and safely looks up the handler, ignoring any undefined names.
"""
name, args = string.split('_~_')
args = args.split(';;;')
func = window.handlers[name]

# safe lookup—if there's no handler for this name, just no-op
func = window.handlers.get(name)
if func is None:
import logging
logging.getLogger(__name__).debug(
"parse_event_message: no handler for '%s', ignoring", name
)
# return a no-op plus empty args
return (lambda *a, **kw: None, ())

return func, args


Expand Down Expand Up @@ -137,7 +152,9 @@ def jbool(b: bool): return 'true' if b is True else 'false' if b is False else N

FLOAT = Literal['left', 'right', 'top', 'bottom']

CANDLE_SHAPE = Literal['Rectangle','Rounded','Ellipse','Arrow','Polygon','Bar','3d',]
CANDLE_SHAPE = Literal['Rectangle','Rounded','Ellipse','Arrow','Polygon','Bar','3d']

TOOLBOX_MODE = Literal['default', 'legacy', 'disabled']

def as_enum(value, string_types):
types = string_types.__args__
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading