diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 57507ed..1f0097b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,9 +16,8 @@ cache: &global_cache # Set up the pipeline stages. stages: - prepare - - test - install - - examples + - test - documentation - deploy - finish @@ -51,6 +50,11 @@ after_script: fi +#----------------------------------------------------------------------- +# Stage: Prepare +#----------------------------------------------------------------------- + + # Create the virtual environment, install the requirements, and save the # environment to the cache. install requirements: @@ -79,6 +83,44 @@ install requirements: -r doc/requirements.txt +#----------------------------------------------------------------------- +# Stage: Install +#----------------------------------------------------------------------- + + +# Test building a distribution. +build distribution: + stage: install + needs: ["install requirements"] + timeout: 5m + cache: + <<: *global_cache + policy: pull + script: + - python3 -m pip wheel --no-deps -w dist . + artifacts: + name: "shelllogger-dist" + paths: + - dist/shelllogger*.whl + expire_in: 6 weeks + + +# Test installation of the package. +install package: + stage: install + needs: ["install requirements"] + timeout: 5m + cache: + <<: *global_cache + script: + - python3 -m pip install . + + +#----------------------------------------------------------------------- +# Stage: Test +#----------------------------------------------------------------------- + + # Execute the unit tests. pytest: stage: test @@ -116,32 +158,29 @@ flake8: --exclude=venv-clean-python -# Test building a distribution. -build distribution: - stage: install - needs: ["install requirements"] +# Ensure the examples run without problems. +examples: + stage: test + needs: ["install requirements", "install package"] timeout: 5m cache: <<: *global_cache policy: pull script: - - python3 -m pip wheel --no-deps -w dist . + - cd examples + - python3 -m pip list + - python3 ./hello_world_html.py + - python3 ./hello_world_html_and_console.py + - python3 ./hello_world_html_with_stats.py + - python3 ./build_flex.py artifacts: - name: "shelllogger-dist" paths: - - dist/shelllogger*.whl - expire_in: 6 weeks + - examples/log* -# Test installation of the package. -install package: - stage: install - needs: ["install requirements"] - timeout: 5m - cache: - <<: *global_cache - script: - - python3 -m pip install . +#----------------------------------------------------------------------- +# Stage: Documentation +#----------------------------------------------------------------------- # Generate the documentation. @@ -159,6 +198,11 @@ sphinx: - doc/html +#----------------------------------------------------------------------- +# Stage: Deploy +#----------------------------------------------------------------------- + + # Publish coverage data and documentation (if on the main branch). pages: stage: deploy @@ -178,6 +222,11 @@ pages: - public +#----------------------------------------------------------------------- +# Stage: Finish +#----------------------------------------------------------------------- + + # Test that uninstalling from a virtual environment works. uninstall package: stage: finish diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..5ace414 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -3,4 +3,4 @@ - \ No newline at end of file + diff --git a/doc/source/images/expand_command_card.png b/doc/source/images/expand_command_card.png new file mode 100644 index 0000000..f8a8700 Binary files /dev/null and b/doc/source/images/expand_command_card.png differ diff --git a/doc/source/images/expand_details.png b/doc/source/images/expand_details.png new file mode 100644 index 0000000..c25ae08 Binary files /dev/null and b/doc/source/images/expand_details.png differ diff --git a/doc/source/images/expand_diagnostics.png b/doc/source/images/expand_diagnostics.png new file mode 100644 index 0000000..e64b6af Binary files /dev/null and b/doc/source/images/expand_diagnostics.png differ diff --git a/doc/source/images/html_log.png b/doc/source/images/html_log.png new file mode 100644 index 0000000..cd697a8 Binary files /dev/null and b/doc/source/images/html_log.png differ diff --git a/doc/source/images/log_dir_tree.png b/doc/source/images/log_dir_tree.png new file mode 100644 index 0000000..6e1cbcc Binary files /dev/null and b/doc/source/images/log_dir_tree.png differ diff --git a/doc/source/index.rst b/doc/source/index.rst index 3126f38..72027f4 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -76,11 +76,107 @@ These data are collected in a "log book". When you call :func:`ShellLogger.finalize`, the contents of the log book are written to a HTML log file. -Example -^^^^^^^ +Example 1: The Basics +^^^^^^^^^^^^^^^^^^^^^^ -.. todo:: +This first example demonstrates the bare-bones basics of ``shelllogger``, in +which we're logging commands only to the HTML log file. - Insert simplest example here, and link to other examples in the repo. +.. literalinclude:: ../../examples/hello_world_html.py + :language: python + :linenos: + :caption: ``examples/hello_world_html.py`` + +Running the script yields + +.. code-block:: + + This example demonstrates logging information solely to the HTML log file. + Open /Users/jmgate/workspace/ShellLogger/examples/log_hello_world_html/2021-09-22_14.56.42.558599_szt68acx/Hello_ShellLogger.html to view the log. + +When you open the HTML log file, you'll see something like + +.. image:: images/html_log.png + :alt: HTML log file + :align: center + +When you first open the log file, most of the content will be collapsed. You +can click on any of the commands to expand it. + +.. image:: images/expand_command_card.png + :alt: Expanding the command card + :align: center + +Here you can see some details, along with ``stdout`` and ``stderr``. Clicking +on **Details** yields even more information: + +.. image:: images/expand_details.png + :alt: Expanding the Details section + :align: center + +Similarly, clicking **Diagnostics** gives you even more information: + +.. image:: images/expand_diagnostics.png + :alt: Expanding the Diagnostics section + :align: center + +Note that some of the cards allow you to search the output via a regular +expression, which can be really helpful when debugging. + +If you look inside the log directory that's created by :class:`ShellLogger`, +you'll see the following: + +.. image:: images/log_dir_tree.png + :alt: Log directory structure + :align: center + +First you'll see a timestamped log directory. Any future runs of the script +will create additional timestamped log directories as siblings. This is to +ensure you never accidentally overwrite a prior log. Within the timestamped +log directory, you'll notice a number of files that contain the ``stdout`` and +``stderr`` streams from the commands that were executed. Then there's the +HTML log file itself, which you can open in a browser, and finally a JSON log +file that's used to construct the HTML log file, mapping the commands to their +output streams. The last item is a symbolic link to the latest HTML log file. +If the script is run multiple times, this link is updated to always point to +the latest log. + +Example 2: Simultaneous Logging to the Console +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This next example demonstrates logging commands both to the HTML log file and +the console. The primary differences between this and **Example 1** are the +highlighted lines below. + +.. literalinclude:: ../../examples/hello_world_html_and_console.py + :language: python + :linenos: + :emphasize-lines: 11, 13 + :caption: ``examples/hello_world_html_and_console.py`` + +Example 3: Collecting Statistics +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In this example, we demonstrate how easy it is to capture various statistics +while running your commands in the shell. The primary differences between this +and **Example 1** are the highlighted lines below. + +.. literalinclude:: ../../examples/hello_world_html_with_stats.py + :language: python + :linenos: + :emphasize-lines: 12, 14 + :caption: ``examples/hello_world_html_with_stats.py`` + +Example 4: Building a Code +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In this final example, we use ``shelllogger`` to do some "real work"---cloning, +configuring, and building `flex `_, a fast +lexical analyzer generator. + +.. literalinclude:: ../../examples/build_flex.py + :language: python + :linenos: + :caption: ``examples/build_flex.py`` For more detailed usage information, see the :doc:`ShellLogger`. diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..62adf05 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1,2 @@ +log* +flex* diff --git a/examples/build_flex.py b/examples/build_flex.py new file mode 100755 index 0000000..64c668a --- /dev/null +++ b/examples/build_flex.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from shelllogger import ShellLogger + +sl = ShellLogger("Build Flex", + Path.cwd() / f"log_{Path(__file__).stem}") +sl.print("This example demonstrates cloning, configuring, and building the " + "Flex tool.") +sl.log("Clone the Flex repository.", + "git clone --depth 1 --branch flex-2.5.39 " + "/~https://github.com/westes/flex.git flex-2.5.39", live_stdout=True, + live_stderr=True) +sl.log("Run Autogen", "./autogen.sh", cwd=Path.cwd()/"flex-2.5.39", + live_stdout=True, live_stderr=True) +sl.log("Configure Flex", "./configure --prefix=$(dirname $(pwd))/flex", + cwd=Path.cwd()/"flex-2.5.39", live_stdout=True, live_stderr=True, + measure=["cpu", "memory", "disk"]) +sl.log("Build libcompat.la", "make libcompat.la", + cwd=Path.cwd()/"flex-2.5.39/lib", live_stdout=True, live_stderr=True, + measure=["cpu", "memory", "disk"]) +sl.log("Build & Install Flex", "make install-exec", + cwd=Path.cwd()/"flex-2.5.39", live_stdout=True, live_stderr=True, + measure=["cpu", "memory", "disk"]) +sl.finalize() +print(f"Open {sl.html_file} to view the log.") diff --git a/examples/hello_world_html.py b/examples/hello_world_html.py new file mode 100755 index 0000000..fa2a33d --- /dev/null +++ b/examples/hello_world_html.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from shelllogger import ShellLogger + +sl = ShellLogger("Hello ShellLogger", + Path.cwd() / f"log_{Path(__file__).stem}") +sl.print("This example demonstrates logging information solely to the HTML " + "log file.") +sl.log("Greet everyone to make them feel welcome.", "echo 'Hello World'") +sl.log("Tell everyone who you are, but from a different directory.", "whoami", + cwd=Path.cwd().parent) +sl.finalize() +print(f"Open {sl.html_file} to view the log.") diff --git a/examples/hello_world_html_and_console.py b/examples/hello_world_html_and_console.py new file mode 100755 index 0000000..000c0b0 --- /dev/null +++ b/examples/hello_world_html_and_console.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from shelllogger import ShellLogger + +sl = ShellLogger("Hello ShellLogger", + Path.cwd() / f"log_{Path(__file__).stem}") +sl.print("This example demonstrates logging information both to the HTML log " + "file and to the console simultaneously.") +sl.log("Greet everyone to make them feel welcome.", "echo 'Hello World'", + live_stdout=True, live_stderr=True) +sl.log("Tell everyone who you are, but from a different directory.", "whoami", + cwd=Path.cwd().parent, live_stdout=True, live_stderr=True) +sl.finalize() +print(f"Open {sl.html_file} to view the log.") diff --git a/examples/hello_world_html_with_stats.py b/examples/hello_world_html_with_stats.py new file mode 100755 index 0000000..aa1d63a --- /dev/null +++ b/examples/hello_world_html_with_stats.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 + +from pathlib import Path +from shelllogger import ShellLogger + +sl = ShellLogger("Hello ShellLogger", + Path.cwd() / f"log_{Path(__file__).stem}") +sl.print("This example demonstrates logging information solely to the HTML " + "log file, while collecting CPU, memory, and disk statistics at the " + "same time.") +sl.log("Greet everyone to make them feel welcome.", "echo 'Hello World'", + measure=["cpu", "memory", "disk"]) +sl.log("Tell everyone who you are, but from a different directory.", "whoami", + cwd=Path.cwd().parent, measure=["cpu", "memory", "disk"]) +sl.finalize() +print(f"Open {sl.html_file} to view the log.")