From b264cb884a5611c36226bb8070b4dd5d6c4ee688 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:08:14 +0200 Subject: chore: move subplot/runcmd to subplot/vendored This makes it clearer they originate elsewhere. --- subplot/runcmd.py | 243 ------------------------------------------- subplot/runcmd.yaml | 83 --------------- subplot/vendored/runcmd.py | 243 +++++++++++++++++++++++++++++++++++++++++++ subplot/vendored/runcmd.yaml | 83 +++++++++++++++ 4 files changed, 326 insertions(+), 326 deletions(-) delete mode 100644 subplot/runcmd.py delete mode 100644 subplot/runcmd.yaml create mode 100644 subplot/vendored/runcmd.py create mode 100644 subplot/vendored/runcmd.yaml diff --git a/subplot/runcmd.py b/subplot/runcmd.py deleted file mode 100644 index 532b60b..0000000 --- a/subplot/runcmd.py +++ /dev/null @@ -1,243 +0,0 @@ -import logging -import os -import re -import shlex -import subprocess - - -# -# Helper functions. -# - -# Get exit code or other stored data about the latest command run by -# runcmd_run. - - -def _runcmd_get(ctx, name): - ns = ctx.declare("_runcmd") - return ns[name] - - -def runcmd_get_exit_code(ctx): - return _runcmd_get(ctx, "exit") - - -def runcmd_get_stdout(ctx): - return _runcmd_get(ctx, "stdout") - - -def runcmd_get_stdout_raw(ctx): - return _runcmd_get(ctx, "stdout.raw") - - -def runcmd_get_stderr(ctx): - return _runcmd_get(ctx, "stderr") - - -def runcmd_get_stderr_raw(ctx): - return _runcmd_get(ctx, "stderr.raw") - - -def runcmd_get_argv(ctx): - return _runcmd_get(ctx, "argv") - - -# Run a command, given an argv and other arguments for subprocess.Popen. -# -# This is meant to be a helper function, not bound directly to a step. The -# stdout, stderr, and exit code are stored in the "_runcmd" namespace in the -# ctx context. -def runcmd_run(ctx, argv, **kwargs): - ns = ctx.declare("_runcmd") - env = dict(os.environ) - pp = ns.get("path-prefix") - if pp: - env["PATH"] = pp + ":" + env["PATH"] - - logging.debug(f"runcmd_run") - logging.debug(f" argv: {argv}") - logging.debug(f" env: {env}") - p = subprocess.Popen( - argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, **kwargs - ) - stdout, stderr = p.communicate("") - ns["argv"] = argv - ns["stdout.raw"] = stdout - ns["stderr.raw"] = stderr - ns["stdout"] = stdout.decode("utf-8") - ns["stderr"] = stderr.decode("utf-8") - ns["exit"] = p.returncode - logging.debug(f" ctx: {ctx}") - logging.debug(f" ns: {ns}") - - -# Step: prepend srcdir to PATH whenever runcmd runs a command. -def runcmd_helper_srcdir_path(ctx): - srcdir = globals()["srcdir"] - runcmd_prepend_to_path(ctx, srcdir) - - -# Step: This creates a helper script. -def runcmd_helper_script(ctx, filename=None): - get_file = globals()["get_file"] - with open(filename, "wb") as f: - f.write(get_file(filename)) - - -# -# Step functions for running commands. -# - - -def runcmd_prepend_to_path(ctx, dirname=None): - ns = ctx.declare("_runcmd") - pp = ns.get("path-prefix", "") - if pp: - pp = f"{pp}:{dirname}" - else: - pp = dirname - ns["path-prefix"] = pp - - -def runcmd_step(ctx, argv0=None, args=None): - runcmd_try_to_run(ctx, argv0=argv0, args=args) - runcmd_exit_code_is_zero(ctx) - - -def runcmd_try_to_run(ctx, argv0=None, args=None): - argv = [shlex.quote(argv0)] + shlex.split(args) - runcmd_run(ctx, argv) - - -# -# Step functions for examining exit codes. -# - - -def runcmd_exit_code_is_zero(ctx): - runcmd_exit_code_is(ctx, exit=0) - - -def runcmd_exit_code_is(ctx, exit=None): - assert_eq = globals()["assert_eq"] - assert_eq(runcmd_get_exit_code(ctx), int(exit)) - - -def runcmd_exit_code_is_nonzero(ctx): - runcmd_exit_code_is_not(ctx, exit=0) - - -def runcmd_exit_code_is_not(ctx, exit=None): - assert_ne = globals()["assert_ne"] - assert_ne(runcmd_get_exit_code(ctx), int(exit)) - - -# -# Step functions and helpers for examining output in various ways. -# - - -def runcmd_stdout_is(ctx, text=None): - _runcmd_output_is(runcmd_get_stdout(ctx), text) - - -def runcmd_stdout_isnt(ctx, text=None): - _runcmd_output_isnt(runcmd_get_stdout(ctx), text) - - -def runcmd_stderr_is(ctx, text=None): - _runcmd_output_is(runcmd_get_stderr(ctx), text) - - -def runcmd_stderr_isnt(ctx, text=None): - _runcmd_output_isnt(runcmd_get_stderr(ctx), text) - - -def _runcmd_output_is(actual, wanted): - assert_eq = globals()["assert_eq"] - wanted = bytes(wanted, "utf8").decode("unicode_escape") - logging.debug("_runcmd_output_is:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" wanted: {wanted!r}") - assert_eq(actual, wanted) - - -def _runcmd_output_isnt(actual, wanted): - assert_ne = globals()["assert_ne"] - wanted = bytes(wanted, "utf8").decode("unicode_escape") - logging.debug("_runcmd_output_isnt:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" wanted: {wanted!r}") - assert_ne(actual, wanted) - - -def runcmd_stdout_contains(ctx, text=None): - _runcmd_output_contains(runcmd_get_stdout(ctx), text) - - -def runcmd_stdout_doesnt_contain(ctx, text=None): - _runcmd_output_doesnt_contain(runcmd_get_stdout(ctx), text) - - -def runcmd_stderr_contains(ctx, text=None): - _runcmd_output_contains(runcmd_get_stderr(ctx), text) - - -def runcmd_stderr_doesnt_contain(ctx, text=None): - _runcmd_output_doesnt_contain(runcmd_get_stderr(ctx), text) - - -def _runcmd_output_contains(actual, wanted): - assert_eq = globals()["assert_eq"] - wanted = bytes(wanted, "utf8").decode("unicode_escape") - logging.debug("_runcmd_output_contains:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" wanted: {wanted!r}") - assert_eq(wanted in actual, True) - - -def _runcmd_output_doesnt_contain(actual, wanted): - assert_ne = globals()["assert_ne"] - wanted = bytes(wanted, "utf8").decode("unicode_escape") - logging.debug("_runcmd_output_doesnt_contain:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" wanted: {wanted!r}") - assert_ne(wanted in actual, True) - - -def runcmd_stdout_matches_regex(ctx, regex=None): - _runcmd_output_matches_regex(runcmd_get_stdout(ctx), regex) - - -def runcmd_stdout_doesnt_match_regex(ctx, regex=None): - _runcmd_output_doesnt_match_regex(runcmd_get_stdout(ctx), regex) - - -def runcmd_stderr_matches_regex(ctx, regex=None): - _runcmd_output_matches_regex(runcmd_get_stderr(ctx), regex) - - -def runcmd_stderr_doesnt_match_regex(ctx, regex=None): - _runcmd_output_doesnt_match_regex(runcmd_get_stderr(ctx), regex) - - -def _runcmd_output_matches_regex(actual, regex): - assert_ne = globals()["assert_ne"] - r = re.compile(regex) - m = r.search(actual) - logging.debug("_runcmd_output_matches_regex:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" regex: {regex!r}") - logging.debug(f" match: {m}") - assert_ne(m, None) - - -def _runcmd_output_doesnt_match_regex(actual, regex): - assert_eq = globals()["assert_eq"] - r = re.compile(regex) - m = r.search(actual) - logging.debug("_runcmd_output_doesnt_match_regex:") - logging.debug(f" actual: {actual!r}") - logging.debug(f" regex: {regex!r}") - logging.debug(f" match: {m}") - assert_eq(m, None) diff --git a/subplot/runcmd.yaml b/subplot/runcmd.yaml deleted file mode 100644 index 48dde90..0000000 --- a/subplot/runcmd.yaml +++ /dev/null @@ -1,83 +0,0 @@ -# Steps to run commands. - -- given: helper script {filename} for runcmd - function: runcmd_helper_script - -- given: srcdir is in the PATH - function: runcmd_helper_srcdir_path - -- when: I run (?P\S+)(?P.*) - regex: true - function: runcmd_step - -- when: I try to run (?P\S+)(?P.*) - regex: true - function: runcmd_try_to_run - -# Steps to examine exit code of latest command. - -- then: exit code is {exit} - function: runcmd_exit_code_is - -- then: exit code is not {exit} - function: runcmd_exit_code_is_not - -- then: command is successful - function: runcmd_exit_code_is_zero - -- then: command fails - function: runcmd_exit_code_is_nonzero - -# Steps to examine stdout/stderr for exact content. - -- then: stdout is exactly "(?P.*)" - regex: true - function: runcmd_stdout_is - -- then: "stdout isn't exactly \"(?P.*)\"" - regex: true - function: runcmd_stdout_isnt - -- then: stderr is exactly "(?P.*)" - regex: true - function: runcmd_stderr_is - -- then: "stderr isn't exactly \"(?P.*)\"" - regex: true - function: runcmd_stderr_isnt - -# Steps to examine stdout/stderr for sub-strings. - -- then: stdout contains "(?P.*)" - regex: true - function: runcmd_stdout_contains - -- then: "stdout doesn't contain \"(?P.*)\"" - regex: true - function: runcmd_stdout_doesnt_contain - -- then: stderr contains "(?P.*)" - regex: true - function: runcmd_stderr_contains - -- then: "stderr doesn't contain \"(?P.*)\"" - regex: true - function: runcmd_stderr_doesnt_contain - -# Steps to match stdout/stderr against regular expressions. - -- then: stdout matches regex (?P.*) - regex: true - function: runcmd_stdout_matches_regex - -- then: stdout doesn't match regex (?P.*) - regex: true - function: runcmd_stdout_doesnt_match_regex - -- then: stderr matches regex (?P.*) - regex: true - function: runcmd_stderr_matches_regex - -- then: stderr doesn't match regex (?P.*) - regex: true - function: runcmd_stderr_doesnt_match_regex diff --git a/subplot/vendored/runcmd.py b/subplot/vendored/runcmd.py new file mode 100644 index 0000000..532b60b --- /dev/null +++ b/subplot/vendored/runcmd.py @@ -0,0 +1,243 @@ +import logging +import os +import re +import shlex +import subprocess + + +# +# Helper functions. +# + +# Get exit code or other stored data about the latest command run by +# runcmd_run. + + +def _runcmd_get(ctx, name): + ns = ctx.declare("_runcmd") + return ns[name] + + +def runcmd_get_exit_code(ctx): + return _runcmd_get(ctx, "exit") + + +def runcmd_get_stdout(ctx): + return _runcmd_get(ctx, "stdout") + + +def runcmd_get_stdout_raw(ctx): + return _runcmd_get(ctx, "stdout.raw") + + +def runcmd_get_stderr(ctx): + return _runcmd_get(ctx, "stderr") + + +def runcmd_get_stderr_raw(ctx): + return _runcmd_get(ctx, "stderr.raw") + + +def runcmd_get_argv(ctx): + return _runcmd_get(ctx, "argv") + + +# Run a command, given an argv and other arguments for subprocess.Popen. +# +# This is meant to be a helper function, not bound directly to a step. The +# stdout, stderr, and exit code are stored in the "_runcmd" namespace in the +# ctx context. +def runcmd_run(ctx, argv, **kwargs): + ns = ctx.declare("_runcmd") + env = dict(os.environ) + pp = ns.get("path-prefix") + if pp: + env["PATH"] = pp + ":" + env["PATH"] + + logging.debug(f"runcmd_run") + logging.debug(f" argv: {argv}") + logging.debug(f" env: {env}") + p = subprocess.Popen( + argv, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, **kwargs + ) + stdout, stderr = p.communicate("") + ns["argv"] = argv + ns["stdout.raw"] = stdout + ns["stderr.raw"] = stderr + ns["stdout"] = stdout.decode("utf-8") + ns["stderr"] = stderr.decode("utf-8") + ns["exit"] = p.returncode + logging.debug(f" ctx: {ctx}") + logging.debug(f" ns: {ns}") + + +# Step: prepend srcdir to PATH whenever runcmd runs a command. +def runcmd_helper_srcdir_path(ctx): + srcdir = globals()["srcdir"] + runcmd_prepend_to_path(ctx, srcdir) + + +# Step: This creates a helper script. +def runcmd_helper_script(ctx, filename=None): + get_file = globals()["get_file"] + with open(filename, "wb") as f: + f.write(get_file(filename)) + + +# +# Step functions for running commands. +# + + +def runcmd_prepend_to_path(ctx, dirname=None): + ns = ctx.declare("_runcmd") + pp = ns.get("path-prefix", "") + if pp: + pp = f"{pp}:{dirname}" + else: + pp = dirname + ns["path-prefix"] = pp + + +def runcmd_step(ctx, argv0=None, args=None): + runcmd_try_to_run(ctx, argv0=argv0, args=args) + runcmd_exit_code_is_zero(ctx) + + +def runcmd_try_to_run(ctx, argv0=None, args=None): + argv = [shlex.quote(argv0)] + shlex.split(args) + runcmd_run(ctx, argv) + + +# +# Step functions for examining exit codes. +# + + +def runcmd_exit_code_is_zero(ctx): + runcmd_exit_code_is(ctx, exit=0) + + +def runcmd_exit_code_is(ctx, exit=None): + assert_eq = globals()["assert_eq"] + assert_eq(runcmd_get_exit_code(ctx), int(exit)) + + +def runcmd_exit_code_is_nonzero(ctx): + runcmd_exit_code_is_not(ctx, exit=0) + + +def runcmd_exit_code_is_not(ctx, exit=None): + assert_ne = globals()["assert_ne"] + assert_ne(runcmd_get_exit_code(ctx), int(exit)) + + +# +# Step functions and helpers for examining output in various ways. +# + + +def runcmd_stdout_is(ctx, text=None): + _runcmd_output_is(runcmd_get_stdout(ctx), text) + + +def runcmd_stdout_isnt(ctx, text=None): + _runcmd_output_isnt(runcmd_get_stdout(ctx), text) + + +def runcmd_stderr_is(ctx, text=None): + _runcmd_output_is(runcmd_get_stderr(ctx), text) + + +def runcmd_stderr_isnt(ctx, text=None): + _runcmd_output_isnt(runcmd_get_stderr(ctx), text) + + +def _runcmd_output_is(actual, wanted): + assert_eq = globals()["assert_eq"] + wanted = bytes(wanted, "utf8").decode("unicode_escape") + logging.debug("_runcmd_output_is:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" wanted: {wanted!r}") + assert_eq(actual, wanted) + + +def _runcmd_output_isnt(actual, wanted): + assert_ne = globals()["assert_ne"] + wanted = bytes(wanted, "utf8").decode("unicode_escape") + logging.debug("_runcmd_output_isnt:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" wanted: {wanted!r}") + assert_ne(actual, wanted) + + +def runcmd_stdout_contains(ctx, text=None): + _runcmd_output_contains(runcmd_get_stdout(ctx), text) + + +def runcmd_stdout_doesnt_contain(ctx, text=None): + _runcmd_output_doesnt_contain(runcmd_get_stdout(ctx), text) + + +def runcmd_stderr_contains(ctx, text=None): + _runcmd_output_contains(runcmd_get_stderr(ctx), text) + + +def runcmd_stderr_doesnt_contain(ctx, text=None): + _runcmd_output_doesnt_contain(runcmd_get_stderr(ctx), text) + + +def _runcmd_output_contains(actual, wanted): + assert_eq = globals()["assert_eq"] + wanted = bytes(wanted, "utf8").decode("unicode_escape") + logging.debug("_runcmd_output_contains:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" wanted: {wanted!r}") + assert_eq(wanted in actual, True) + + +def _runcmd_output_doesnt_contain(actual, wanted): + assert_ne = globals()["assert_ne"] + wanted = bytes(wanted, "utf8").decode("unicode_escape") + logging.debug("_runcmd_output_doesnt_contain:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" wanted: {wanted!r}") + assert_ne(wanted in actual, True) + + +def runcmd_stdout_matches_regex(ctx, regex=None): + _runcmd_output_matches_regex(runcmd_get_stdout(ctx), regex) + + +def runcmd_stdout_doesnt_match_regex(ctx, regex=None): + _runcmd_output_doesnt_match_regex(runcmd_get_stdout(ctx), regex) + + +def runcmd_stderr_matches_regex(ctx, regex=None): + _runcmd_output_matches_regex(runcmd_get_stderr(ctx), regex) + + +def runcmd_stderr_doesnt_match_regex(ctx, regex=None): + _runcmd_output_doesnt_match_regex(runcmd_get_stderr(ctx), regex) + + +def _runcmd_output_matches_regex(actual, regex): + assert_ne = globals()["assert_ne"] + r = re.compile(regex) + m = r.search(actual) + logging.debug("_runcmd_output_matches_regex:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" regex: {regex!r}") + logging.debug(f" match: {m}") + assert_ne(m, None) + + +def _runcmd_output_doesnt_match_regex(actual, regex): + assert_eq = globals()["assert_eq"] + r = re.compile(regex) + m = r.search(actual) + logging.debug("_runcmd_output_doesnt_match_regex:") + logging.debug(f" actual: {actual!r}") + logging.debug(f" regex: {regex!r}") + logging.debug(f" match: {m}") + assert_eq(m, None) diff --git a/subplot/vendored/runcmd.yaml b/subplot/vendored/runcmd.yaml new file mode 100644 index 0000000..48dde90 --- /dev/null +++ b/subplot/vendored/runcmd.yaml @@ -0,0 +1,83 @@ +# Steps to run commands. + +- given: helper script {filename} for runcmd + function: runcmd_helper_script + +- given: srcdir is in the PATH + function: runcmd_helper_srcdir_path + +- when: I run (?P\S+)(?P.*) + regex: true + function: runcmd_step + +- when: I try to run (?P\S+)(?P.*) + regex: true + function: runcmd_try_to_run + +# Steps to examine exit code of latest command. + +- then: exit code is {exit} + function: runcmd_exit_code_is + +- then: exit code is not {exit} + function: runcmd_exit_code_is_not + +- then: command is successful + function: runcmd_exit_code_is_zero + +- then: command fails + function: runcmd_exit_code_is_nonzero + +# Steps to examine stdout/stderr for exact content. + +- then: stdout is exactly "(?P.*)" + regex: true + function: runcmd_stdout_is + +- then: "stdout isn't exactly \"(?P.*)\"" + regex: true + function: runcmd_stdout_isnt + +- then: stderr is exactly "(?P.*)" + regex: true + function: runcmd_stderr_is + +- then: "stderr isn't exactly \"(?P.*)\"" + regex: true + function: runcmd_stderr_isnt + +# Steps to examine stdout/stderr for sub-strings. + +- then: stdout contains "(?P.*)" + regex: true + function: runcmd_stdout_contains + +- then: "stdout doesn't contain \"(?P.*)\"" + regex: true + function: runcmd_stdout_doesnt_contain + +- then: stderr contains "(?P.*)" + regex: true + function: runcmd_stderr_contains + +- then: "stderr doesn't contain \"(?P.*)\"" + regex: true + function: runcmd_stderr_doesnt_contain + +# Steps to match stdout/stderr against regular expressions. + +- then: stdout matches regex (?P.*) + regex: true + function: runcmd_stdout_matches_regex + +- then: stdout doesn't match regex (?P.*) + regex: true + function: runcmd_stdout_doesnt_match_regex + +- then: stderr matches regex (?P.*) + regex: true + function: runcmd_stderr_matches_regex + +- then: stderr doesn't match regex (?P.*) + regex: true + function: runcmd_stderr_doesnt_match_regex -- cgit v1.2.1 From f32ba06142fab3285f0104492b1d97ef5c60c38a Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:11:38 +0200 Subject: chore: update ewww.md to use vendored runcmd --- ewww.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ewww.md b/ewww.md index 1c5780f..e33f5eb 100644 --- a/ewww.md +++ b/ewww.md @@ -146,12 +146,12 @@ author: Lars Wirzenius template: python bindings: - subplot/ewww.yaml - - subplot/runcmd.yaml + - subplot/vendored/runcmd.yaml functions: - subplot/ewww.py - subplot/daemon.py - subplot/http.py - - subplot/runcmd.py + - subplot/vendored/runcmd.py classes: - scenario-disabled ... -- cgit v1.2.1 From 652cfdf094736d8a53b9176e9a2d574c8c73cc9f Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:10:45 +0200 Subject: chore: update runcmd.py from Subplot --- subplot/vendored/runcmd.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/subplot/vendored/runcmd.py b/subplot/vendored/runcmd.py index 532b60b..a2564c6 100644 --- a/subplot/vendored/runcmd.py +++ b/subplot/vendored/runcmd.py @@ -49,7 +49,16 @@ def runcmd_get_argv(ctx): # ctx context. def runcmd_run(ctx, argv, **kwargs): ns = ctx.declare("_runcmd") + + # The Subplot Python template empties os.environ at startup, modulo a small + # number of variables with carefully chosen values. Here, we don't need to + # care about what those variables are, but we do need to not overwrite + # them, so we just add anything in the env keyword argument, if any, to + # os.environ. env = dict(os.environ) + for key, arg in kwargs.pop("env", {}).items(): + env[key] = arg + pp = ns.get("path-prefix") if pp: env["PATH"] = pp + ":" + env["PATH"] -- cgit v1.2.1 From 80bdaa2e4a12a037d8f6b3a7658289b02df1cd1b Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:12:43 +0200 Subject: chore: vendor lib/daemon from Subplot --- ewww.md | 3 +- subplot/vendored/daemon.py | 256 +++++++++++++++++++++++++++++++++++++++++++ subplot/vendored/daemon.yaml | 37 +++++++ 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 subplot/vendored/daemon.py create mode 100644 subplot/vendored/daemon.yaml diff --git a/ewww.md b/ewww.md index e33f5eb..6352c02 100644 --- a/ewww.md +++ b/ewww.md @@ -146,11 +146,12 @@ author: Lars Wirzenius template: python bindings: - subplot/ewww.yaml + - subplot/vendored/daemon.yaml - subplot/vendored/runcmd.yaml functions: - subplot/ewww.py - - subplot/daemon.py - subplot/http.py + - subplot/vendored/daemon.py - subplot/vendored/runcmd.py classes: - scenario-disabled diff --git a/subplot/vendored/daemon.py b/subplot/vendored/daemon.py new file mode 100644 index 0000000..d436b4f --- /dev/null +++ b/subplot/vendored/daemon.py @@ -0,0 +1,256 @@ +import logging +import os +import signal +import socket +import subprocess +import time + + +# A helper function for testing lib/daemon itself. +def _daemon_shell_script(ctx, filename=None): + get_file = globals()["get_file"] + data = get_file(filename) + with open(filename, "wb") as f: + f.write(data) + os.chmod(filename, 0o755) + + +# Start a daemon that will open a port on localhost. +def daemon_start_on_port(ctx, path=None, args=None, name=None, port=None): + _daemon_start(ctx, path=path, args=args, name=name) + daemon_wait_for_port("localhost", port) + + +# Start a daemon after a little wait. This is used only for testing the +# port-waiting code. +def _daemon_start_soonish(ctx, path=None, args=None, name=None, port=None): + _daemon_start(ctx, path=os.path.abspath(path), args=args, name=name) + daemon = ctx.declare("_daemon") + + # Store the PID of the process we just started so that _daemon_stop_soonish + # can kill it during the cleanup phase. This works around the Subplot + # Python template not giving the step captures to cleanup functions. Note + # that this code assume at most one _soonish function is called. + daemon["_soonish"] = daemon[name]["pid"] + + try: + daemon_wait_for_port("localhost", port) + except Exception as e: + daemon["_start_error"] = repr(e) + + logging.info("pgrep: %r", _daemon_pgrep(path)) + + +def _daemon_stop_soonish(ctx, path=None, args=None, name=None, port=None): + ns = ctx.declare("_daemon") + pid = ns["_soonish"] + logging.debug(f"Stopping soonishly-started daemon, {pid}") + signo = signal.SIGKILL + try: + os.kill(pid, signo) + except ProcessLookupError: + logging.warning("Process did not actually exist (anymore?)") + + +# Start a daeamon, get its PID. Don't wait for a port or anything. This is +# meant for background processes that don't have port. Useful for testing the +# lib/daemon library of Subplot, but not much else. +def _daemon_start(ctx, path=None, args=None, name=None): + runcmd_run = globals()["runcmd_run"] + runcmd_exit_code_is = globals()["runcmd_exit_code_is"] + runcmd_get_exit_code = globals()["runcmd_get_exit_code"] + runcmd_get_stderr = globals()["runcmd_get_stderr"] + runcmd_prepend_to_path = globals()["runcmd_prepend_to_path"] + + path = os.path.abspath(path) + argv = [path] + args.split() + + logging.debug(f"Starting daemon {name}") + logging.debug(f" ctx={ctx.as_dict()}") + logging.debug(f" name={name}") + logging.debug(f" path={path}") + logging.debug(f" args={args}") + logging.debug(f" argv={argv}") + + ns = ctx.declare("_daemon") + + this = ns[name] = { + "pid-file": f"{name}.pid", + "stderr": f"{name}.stderr", + "stdout": f"{name}.stdout", + } + + # Debian installs `daemonize` to /usr/sbin, which isn't part of the minimal + # environment that Subplot sets up. So we add /usr/sbin to the PATH. + runcmd_prepend_to_path(ctx, "/usr/sbin") + runcmd_run( + ctx, + [ + "daemonize", + "-c", + os.getcwd(), + "-p", + this["pid-file"], + "-e", + this["stderr"], + "-o", + this["stdout"], + ] + + argv, + ) + + # Check that daemonize has exited OK. If it hasn't, it didn't start the + # background process at all. If so, log the stderr in case there was + # something useful there for debugging. + exit = runcmd_get_exit_code(ctx) + if exit != 0: + stderr = runcmd_get_stderr(ctx) + logging.error(f"daemon {name} stderr: {stderr}") + runcmd_exit_code_is(ctx, 0) + + # Get the pid of the background process, from the pid file created by + # daemonize. We don't need to wait for it, since we know daemonize already + # exited. If it isn't there now, it's won't appear later. + if not os.path.exists(this["pid-file"]): + raise Exception("daemonize didn't create a PID file") + + this["pid"] = _daemon_wait_for_pid(this["pid-file"], 10.0) + + logging.debug(f"Started daemon {name}") + logging.debug(f" pid={this['pid']}") + logging.debug(f" ctx={ctx.as_dict()}") + + +def _daemon_wait_for_pid(filename, timeout): + start = time.time() + while time.time() < start + timeout: + with open(filename) as f: + data = f.read().strip() + if data: + return int(data) + raise Exception("daemonize created a PID file without a PID") + + +def daemon_wait_for_port(host, port, timeout=5.0): + addr = (host, port) + until = time.time() + timeout + while True: + try: + s = socket.create_connection(addr, timeout=timeout) + s.close() + return + except socket.timeout: + logging.error(f"daemon did not respond at port {port} within {timeout} seconds") + raise + except socket.error as e: + logging.info(f"could not connect to daemon at {port}: {e}") + pass + if time.time() >= until: + logging.error(f"could not connect to daemon at {port} within {timeout} seconds") + raise ConnectionRefusedError() + # Sleep a bit to avoid consuming too much CPU while busy-waiting. + time.sleep(0.1) + + +# Stop a daemon. +def daemon_stop(ctx, path=None, args=None, name=None): + logging.debug(f"Stopping daemon {name}") + + ns = ctx.declare("_daemon") + logging.debug(f" ns={ns}") + pid = ns[name]["pid"] + signo = signal.SIGTERM + + this = ns[name] + data = open(this["stdout"]).read() + logging.debug(f"{name} stdout, before: {data!r}") + data = open(this["stderr"]).read() + logging.debug(f"{name} stderr, before: {data!r}") + + logging.debug(f"Terminating process {pid} with signal {signo}") + try: + os.kill(pid, signo) + except ProcessLookupError: + logging.warning("Process did not actually exist (anymore?)") + + while True: + try: + os.kill(pid, 0) + logging.debug(f"Daemon {name}, pid {pid} still exists") + time.sleep(1) + except ProcessLookupError: + break + logging.debug(f"Daemon {name} is gone") + + data = open(this["stdout"]).read() + logging.debug(f"{name} stdout, after: {data!r}") + data = open(this["stderr"]).read() + logging.debug(f"{name} stderr, after: {data!r}") + + +def daemon_no_such_process(ctx, args=None): + assert not _daemon_pgrep(args) + + +def daemon_process_exists(ctx, args=None): + assert _daemon_pgrep(args) + + +def _daemon_pgrep(pattern): + logging.info(f"checking if process exists: pattern={pattern}") + exit = subprocess.call(["pgrep", "-laf", pattern], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + logging.info(f"exit code: {exit}") + return exit == 0 + + +def daemon_start_fails_with(ctx, message=None): + daemon = ctx.declare("_daemon") + error = daemon["_start_error"] + logging.debug(f"daemon_start_fails_with: error={error!r}") + logging.debug(f"daemon_start_fails_with: message={message!r}") + assert message.lower() in error.lower() + + +def daemon_get_stdout(ctx, name): + return _daemon_get_output(ctx, name, "stdout") + + +def daemon_get_stderr(ctx, name): + return _daemon_get_output(ctx, name, "stderr") + + +def _daemon_get_output(ctx, name, which): + ns = ctx.declare("_daemon") + this = ns[name] + filename = this[which] + data = open(filename).read() + logging.debug(f"Read {which} of daemon {name} from {filename}: {data!r}") + return data + + +def daemon_has_produced_output(ctx, name=None): + started = time.time() + timeout = 5.0 + while time.time() < started + timeout: + stdout = daemon_get_stdout(ctx, name) + stderr = daemon_get_stderr(ctx, name) + if stdout and stderr: + break + time.sleep(0.1) + + +def daemon_stdout_is(ctx, name=None, text=None): + daemon_get_stdout = globals()["daemon_get_stdout"] + _daemon_output_is(ctx, name, text, daemon_get_stdout) + + +def daemon_stderr_is(ctx, name=None, text=None): + daemon_get_stderr = globals()["daemon_get_stderr"] + _daemon_output_is(ctx, name, text, daemon_get_stderr) + + +def _daemon_output_is(ctx, name, text, getter): + assert_eq = globals()["assert_eq"] + text = bytes(text, "UTF-8").decode("unicode_escape") + output = getter(ctx, name) + assert_eq(text, output) diff --git a/subplot/vendored/daemon.yaml b/subplot/vendored/daemon.yaml new file mode 100644 index 0000000..4fab1f6 --- /dev/null +++ b/subplot/vendored/daemon.yaml @@ -0,0 +1,37 @@ +- given: there is no "{args:text}" process + function: daemon_no_such_process + +- given: a daemon helper shell script {filename} + function: _daemon_shell_script + +- when: I start "{path}{args:text}" as a background process as {name}, on port {port} + function: daemon_start_on_port + +- when: I try to start "{path}{args:text}" as {name}, on port {port} + function: _daemon_start_soonish + cleanup: _daemon_stop_soonish + +- when: I start "{path}{args:text}" as a background process as {name} + function: _daemon_start + +- when: I stop background process {name} + function: daemon_stop + +- when: daemon {name} has produced output + function: daemon_has_produced_output + +- then: a process "{args:text}" is running + function: daemon_process_exists + +- then: there is no "{args:text}" process + function: daemon_no_such_process + +- then: starting daemon fails with "{message:text}" + function: daemon_start_fails_with + +- then: daemon {name} stdout is "{text:text}" + function: daemon_stdout_is + +- then: daemon {name} stderr is "{text:text}" + function: daemon_stderr_is + -- cgit v1.2.1 From ef171a1cfde855704459f87d921ca245434d781f Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:19:22 +0200 Subject: refactor: use subplot's lib/daemon in ewww.py --- subplot/ewww.py | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/subplot/ewww.py b/subplot/ewww.py index 43e5946..d53b74c 100644 --- a/subplot/ewww.py +++ b/subplot/ewww.py @@ -5,8 +5,6 @@ import logging import os import random import shutil -import socket -import time import urllib.parse import yaml @@ -62,7 +60,8 @@ def copy_test_certificate(ctx, cert=None, key=None): # Start server using named configuration file. def start_server(ctx, filename=None): get_file = globals()["get_file"] - start_daemon = globals()["start_daemon"] + daemon_start_on_port = globals()["daemon_start_on_port"] + logging.debug(f"Starting ewww with config file {filename}") config = get_file(filename).decode("UTF-8") config = yaml.safe_load(config) @@ -72,32 +71,16 @@ def start_server(ctx, filename=None): config = yaml.safe_dump(config) _write(filename, config) - start_daemon(ctx, "ewww", [_binary("ewww"), filename]) - - if not port_open("localhost", port, 5.0): - stderr = open(ctx["daemon"]["ewww"]["stderr"]).read() - logging.debug(f"Stderr from daemon: {stderr!r}") - - -# Wait for a port to be open -def port_open(host, port, timeout): - logging.debug(f"Waiting for port localhost:{port} to be available") - started = time.time() - while time.time() < started + timeout: - try: - socket.create_connection((host, port), timeout=timeout) - return True - except socket.error: - pass - logging.error(f"Port localhost:{port} is not open") - return False + daemon_start_on_port( + ctx, path=_binary("ewww"), args=filename, name="ewww", port=port + ) # Stop previously started server. def stop_server(ctx): - stop_daemon = globals()["stop_daemon"] + daemon_stop = globals()["daemon_stop"] logging.debug("Stopping ewww") - stop_daemon(ctx, "ewww") + daemon_stop(ctx, name="ewww") # Make an HTTP request. -- cgit v1.2.1 From d0e404c6fe86cd4771ab11e1b458ca6c1d6e93cf Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:19:52 +0200 Subject: chore: vendor Subplot's lib/files --- subplot/vendored/files.py | 158 ++++++++++++++++++++++++++++++++++++++++++++ subplot/vendored/files.yaml | 62 +++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 subplot/vendored/files.py create mode 100644 subplot/vendored/files.yaml diff --git a/subplot/vendored/files.py b/subplot/vendored/files.py new file mode 100644 index 0000000..ec37b9d --- /dev/null +++ b/subplot/vendored/files.py @@ -0,0 +1,158 @@ +import logging +import os +import re +import time + + +def files_create_from_embedded(ctx, filename=None): + files_create_from_embedded_with_other_name( + ctx, filename_on_disk=filename, embedded_filename=filename + ) + + +def files_create_from_embedded_with_other_name( + ctx, filename_on_disk=None, embedded_filename=None +): + get_file = globals()["get_file"] + with open(filename_on_disk, "wb") as f: + f.write(get_file(embedded_filename)) + + +def files_create_from_text(ctx, filename=None, text=None): + with open(filename, "w") as f: + f.write(text) + + +def files_file_exists(ctx, filename=None): + assert_eq = globals()["assert_eq"] + assert_eq(os.path.exists(filename), True) + + +def files_file_does_not_exist(ctx, filename=None): + assert_eq = globals()["assert_eq"] + assert_eq(os.path.exists(filename), False) + + +def files_only_these_exist(ctx, filenames=None): + assert_eq = globals()["assert_eq"] + filenames = filenames.replace(",", "").split() + assert_eq(set(os.listdir(".")), set(filenames)) + + +def files_file_contains(ctx, filename=None, data=None): + assert_eq = globals()["assert_eq"] + with open(filename, "rb") as f: + actual = f.read() + actual = actual.decode("UTF-8") + assert_eq(data in actual, True) + + +def files_file_matches_regex(ctx, filename=None, regex=None): + assert_eq = globals()["assert_eq"] + with open(filename) as f: + content = f.read() + m = re.search(regex, content) + if m is None: + logging.debug(f"files_file_matches_regex: no match") + logging.debug(f" filenamed: {filename}") + logging.debug(f" regex: {regex}") + logging.debug(f" content: {regex}") + logging.debug(f" match: {m}") + assert_eq(bool(m), True) + + +def files_match(ctx, filename1=None, filename2=None): + assert_eq = globals()["assert_eq"] + with open(filename1, "rb") as f: + data1 = f.read() + with open(filename2, "rb") as f: + data2 = f.read() + assert_eq(data1, data2) + + +def files_touch_with_timestamp( + ctx, + filename=None, + year=None, + month=None, + day=None, + hour=None, + minute=None, + second=None, +): + t = ( + int(year), + int(month), + int(day), + int(hour), + int(minute), + int(second), + -1, + -1, + -1, + ) + ts = time.mktime(t) + _files_touch(filename, ts) + + +def files_touch(ctx, filename=None): + _files_touch(filename, None) + + +def _files_touch(filename, ts): + if not os.path.exists(filename): + open(filename, "w").close() + times = None + if ts is not None: + times = (ts, ts) + os.utime(filename, times=times) + + +def files_mtime_is_recent(ctx, filename=None): + st = os.stat(filename) + age = abs(st.st_mtime - time.time()) + assert age < 1.0 + + +def files_mtime_is_ancient(ctx, filename=None): + st = os.stat(filename) + age = abs(st.st_mtime - time.time()) + year = 365 * 24 * 60 * 60 + required = 39 * year + logging.debug(f"ancient? mtime={st.st_mtime} age={age} required={required}") + assert age > required + + +def files_remember_metadata(ctx, filename=None): + meta = _files_remembered(ctx) + meta[filename] = _files_get_metadata(filename) + logging.debug("files_remember_metadata:") + logging.debug(f" meta: {meta}") + logging.debug(f" ctx: {ctx}") + + +# Check that current metadata of a file is as stored in the context. +def files_has_remembered_metadata(ctx, filename=None): + assert_eq = globals()["assert_eq"] + meta = _files_remembered(ctx) + logging.debug("files_has_remembered_metadata:") + logging.debug(f" meta: {meta}") + logging.debug(f" ctx: {ctx}") + assert_eq(meta[filename], _files_get_metadata(filename)) + + +def files_has_different_metadata(ctx, filename=None): + assert_ne = globals()["assert_ne"] + meta = _files_remembered(ctx) + assert_ne(meta[filename], _files_get_metadata(filename)) + + +def _files_remembered(ctx): + ns = ctx.declare("_files") + return ns.get("remembered-metadata", {}) + + +def _files_get_metadata(filename): + st = os.lstat(filename) + keys = ["st_dev", "st_gid", "st_ino", "st_mode", "st_mtime", "st_size", "st_uid"] + return {key: getattr(st, key) for key in keys} diff --git a/subplot/vendored/files.yaml b/subplot/vendored/files.yaml new file mode 100644 index 0000000..be69920 --- /dev/null +++ b/subplot/vendored/files.yaml @@ -0,0 +1,62 @@ +- given: file {filename} + function: files_create_from_embedded + types: + filename: file + +- given: file {filename_on_disk} from {embedded_filename} + function: files_create_from_embedded_with_other_name + types: + embedded_filename: file + +- given: file {filename} has modification time {year}-{month}-{day} {hour}:{minute}:{second} + function: files_touch_with_timestamp + +- when: I write "(?P.*)" to file (?P\S+) + regex: true + function: files_create_from_text + +- when: I remember metadata for file {filename} + function: files_remember_metadata + +- when: I touch file {filename} + function: files_touch + +- then: file {filename} exists + function: files_file_exists + +- then: file {filename} does not exist + function: files_file_does_not_exist + +- then: only files (?P.+) exist + function: files_only_these_exist + regex: true + +- then: file (?P\S+) contains "(?P.*)" + regex: true + function: files_file_contains + +- then: file (?P\S+) matches regex /(?P.*)/ + regex: true + function: files_file_matches_regex + +- then: file (?P\S+) matches regex "(?P.*)" + regex: true + function: files_file_matches_regex + +- then: files {filename1} and {filename2} match + function: files_match + +- then: file {filename} has same metadata as before + function: files_has_remembered_metadata + +- then: file {filename} has different metadata from before + function: files_has_different_metadata + +- then: file {filename} has changed from before + function: files_has_different_metadata + +- then: file {filename} has a very recent modification time + function: files_mtime_is_recent + +- then: file {filename} has a very old modification time + function: files_mtime_is_ancient -- cgit v1.2.1 From adda6613d7fdf47967d5d55057044ac618d40d1a Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:28:42 +0200 Subject: refactor: use Subplot's lib/files --- ewww.md | 9 ++++++++- subplot/ewww.py | 7 +++++++ subplot/ewww.yaml | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/ewww.md b/ewww.md index 6352c02..d1c9faf 100644 --- a/ewww.md +++ b/ewww.md @@ -92,7 +92,8 @@ for static content only. Every other method returns an error. ~~~scenario given a self-signed certificate as snakeoil.pem, using key snakeoil.key -and file webroot/foo.html with "this is your web page" +and directory webroot +and file webroot/foo.html from webpage.html and a running server using config file smoke.yaml when I request GET https://example.com/foo.html then I get status code 200 @@ -110,6 +111,10 @@ tls_cert: snakeoil.pem tls_key: snakeoil.key ~~~ +~~~{#webpage.html .file add-newline=no} +this is your web page +~~~ + ## Performance test ~~~scenario-disabled @@ -147,11 +152,13 @@ template: python bindings: - subplot/ewww.yaml - subplot/vendored/daemon.yaml + - subplot/vendored/files.yaml - subplot/vendored/runcmd.yaml functions: - subplot/ewww.py - subplot/http.py - subplot/vendored/daemon.py + - subplot/vendored/files.py - subplot/vendored/runcmd.py classes: - scenario-disabled diff --git a/subplot/ewww.py b/subplot/ewww.py index d53b74c..6c93733 100644 --- a/subplot/ewww.py +++ b/subplot/ewww.py @@ -40,6 +40,13 @@ def fixme(*args, **kwargs): assert 0 +# Create a directory. +def create_directory(ctx, dirname=None): + dirname = "./" + dirname + if not os.path.exists(dirname): + os.makedirs(dirname) + + # Create a file. def create_file(ctx, filename=None, content=None): logging.debug(f"Creating file {filename} with {content}") diff --git a/subplot/ewww.yaml b/subplot/ewww.yaml index 375558d..ec5395f 100644 --- a/subplot/ewww.yaml +++ b/subplot/ewww.yaml @@ -8,6 +8,9 @@ - given: "{count} files in {dirname}" function: fixme +- given: directory {dirname} + function: create_directory + - given: file (?P\S+) with "(?P.*)" regex: true function: create_file -- cgit v1.2.1 From 630d13597b69f278a67699c8938ef8721e4423e0 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:29:29 +0200 Subject: refactor: drop now-unnecessary binding --- subplot/ewww.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/subplot/ewww.yaml b/subplot/ewww.yaml index ec5395f..7353863 100644 --- a/subplot/ewww.yaml +++ b/subplot/ewww.yaml @@ -5,16 +5,9 @@ function: start_server cleanup: stop_server -- given: "{count} files in {dirname}" - function: fixme - - given: directory {dirname} function: create_directory -- given: file (?P\S+) with "(?P.*)" - regex: true - function: create_file - - then: I am redirected to {location} function: fixme -- cgit v1.2.1 From 3969f5dfa5e71b64cb0522c22c065bf0858dc1f0 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 10:31:11 +0200 Subject: refactor: drop now-unused function to creaste file --- subplot/ewww.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/subplot/ewww.py b/subplot/ewww.py index 6c93733..f1e02e3 100644 --- a/subplot/ewww.py +++ b/subplot/ewww.py @@ -47,14 +47,6 @@ def create_directory(ctx, dirname=None): os.makedirs(dirname) -# Create a file. -def create_file(ctx, filename=None, content=None): - logging.debug(f"Creating file {filename} with {content}") - dirname = os.path.dirname(filename) - os.makedirs(dirname) - _write(filename, content) - - # Copy test certificate from source tree, where it's been created previously by # ./check. def copy_test_certificate(ctx, cert=None, key=None): -- cgit v1.2.1 From 857e86ccd32a427cce5dd9df6402fd1d458c5c7a Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 30 Jan 2021 09:42:27 +0200 Subject: build: add initial Debian packaging --- debian/cargo-checksum.json | 0 debian/changelog | 6 ++++++ debian/compat | 2 ++ debian/control | 26 ++++++++++++++++++++++++++ debian/copyright | 23 +++++++++++++++++++++++ debian/rules | 15 +++++++++++++++ debian/source/format | 1 + 7 files changed, 73 insertions(+) create mode 100644 debian/cargo-checksum.json create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100755 debian/rules create mode 100644 debian/source/format diff --git a/debian/cargo-checksum.json b/debian/cargo-checksum.json new file mode 100644 index 0000000..e69de29 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..fa45612 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +ewww (0.1-1) unstable; urgency=low + + * Initial packaging. This is not intended to be uploaded to Debian, so + no closing of an ITP bug. + + -- Lars Wirzenius Sat, 28 Sep 2019 16:45:49 +0300 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..021ea30 --- /dev/null +++ b/debian/compat @@ -0,0 +1,2 @@ +10 + diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..91b0975 --- /dev/null +++ b/debian/control @@ -0,0 +1,26 @@ +Source: ewww +Maintainer: Lars Wirzenius +Section: www +Priority: optional +Standards-Version: 4.2.0 +Build-Depends: + debhelper (>= 10~), + build-essential, + dh-cargo, + daemonize, + git, + pkg-config, + python3, + python3-requests, + subplot, + texlive-fonts-recommended, + texlive-latex-base, + texlive-latex-recommended +Homepage: https://obnam.org + +Package: ewww +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends} +Built-Using: ${cargo:Built-Using} +Description: simplistic web server for static content + Ewww serves static web content. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..89db566 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,23 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: ewww +Upstream-Contact: Lars Wirzenius +Source: http://git.liw.fi/ewww + +Files: * +Copyright: 2020, Lars Wirzenius +License: GPL-3+ + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On a Debian system, you can find a copy of GPL version 3 at + /usr/share/common-licenses/GPL-3 . diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..9385a1b --- /dev/null +++ b/debian/rules @@ -0,0 +1,15 @@ +#!/usr/bin/make -f + +%: + dh $@ --buildsystem cargo + +override_dh_auto_build: + true + +override_dh_auto_install: + cargo install --path=. --root=debian/ewww + rm -f debian/ewww/.crates.toml + rm -f debian/ewww/.crates2.json + +override_dh_auto_test: + ./check diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) -- cgit v1.2.1