diff --git a/.gitignore b/.gitignore index 1552802..7d7c3c2 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ _scraps build dist docs/build +help/site htmlcov tmp.* tmp diff --git a/VERSION.txt b/VERSION.txt index 295ec49..8fc3e14 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -6.8.2 \ No newline at end of file +6.8.3 \ No newline at end of file diff --git a/help/docs/commands/django.md b/help/docs/commands/django.md new file mode 100644 index 0000000..1c0a72a --- /dev/null +++ b/help/docs/commands/django.md @@ -0,0 +1,118 @@ +# Django + +Summary: Work with Django management commands. + +## Common Options for Django Commands + +You will generally want to include `cd` to change to the project directory and `prefix` to load the virtual environment. + +```yaml +- collect static files: + django: static + cd: /path/to/project/source + prefix: source ../python/bin/activate +``` + +## Automatic Conversion of Django Command Switches + +Options provided in the command configuration file are automatically converted to command line switches. + +```yaml +- run database migrations: + django: migrate + settings: tenants.example_com.settings + +- dump some data: + django: dumpdata + indent: 4 + natural_foreign: yes + natural_primary: yes +``` + +## Available Commands + +### check + +```ini +[run django checks] +django: check +``` + +```yaml +- run django checks: + django: check +``` + +### dumpdata + +Dump fixture data. + +- app: Required. The name of the app. +- model: Optional. A model name within the app. +- path: The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. + +```ini +[dump project data] +django: dumpdata +app: projects + +[dump project categories] +django: dumpdata +app: projects +model: Category +path: local/projects/fixtures/default-categories.json +``` + +### loaddata + +Load fixture data. + +- path: The path to the JSON file. When a model is provided, this defaults to `fixtures/app/model.json`. Otherwise, it is `fixtures/app/initial.json`. + +### migrate + +Run database migrations. + +```ini +[run database migrations] +django: migrate +``` + +```yaml +- run database migrations: + django: migrate +``` + +### static + +Collect static files. + +```ini +[collect static files] +django: static +``` + +```yaml +- collect static files: + django: static +``` + +## Custom or Ad Hoc Commands + +It is possible to work with any Django management command provided the parameters may be specified as a switch. + +```ini +[run any django command] +django: command_name +first_option_name: asdf +second_option_name: 1234 +third_option_name: yes +``` + +```yaml +- run any django command: + django: command_name + first_option_name: asdf + second_option_name: 1234 + third_option_name: yes +``` diff --git a/help/docs/commands/messages.md b/help/docs/commands/messages.md new file mode 100644 index 0000000..3fff90f --- /dev/null +++ b/help/docs/commands/messages.md @@ -0,0 +1,111 @@ +# Messages + +Summary: Send feedback to users. + +## Available Commands + +### dialog + +Use the dialog CLI to display a message. + +- `height`: The height of the dialog box. Default: `15` +- `title`: An optional title to display as part of the dialog box. Default: `Message`. +- `width`: The width of the dialog box. Default: `100` + +```ini +[send some feedback] +dialog: "This is a message." +``` + +```yaml +- send some feedback: + dialog: "This is a message." +``` + +!!! warning + + The dialog command line utility must be installed. + +### explain + +Provide an explanation. When generating code this is added as a comment. When documentation is generated, it is output as text. + +```ini +[introduction] +explain: "These steps will set up a Radicale CalDav/CardDav server." +header: Introduction +``` + +The `header` option is not used in comments, but makes documentation more readable and facilitates the creation of tutorials or install guides that re-use the defined steps. + +### echo + +Display a simple message. + +```ini +[send some feedback] +echo: "This is a message." +``` + +```yaml +- send some feedback: + echo: "This is a message." +``` + +### slack + +Send a message via Slack. + +- `url`: Required. The URL to which the message should be sent. + +```ini +[send some feedback] +slack: "This is a message." +url: https://subdomain.slack.com/path/to/your/integration +``` + +```yaml +- send some feedback: + slack: "This is a message." + url: https://subdomain.slack.com/path/to/your/integration +``` + +!!! note + You could easily define a variable for the Slack URL and set ``url: {{ slack_url }}`` to save some typing. See [variables](../config/variables.md). + +### screenshot + +Like `explain` above, a screenshot adds detail to comments or documentation, but does not produce a command statement. + +```ini +[login screenshot after successful install] +screenshot: images/login.png +caption: Login Page +height: 50% +width: 50% +``` + +The value of `screenshot` may be relative to the command file or a full URL to the image. If `caption` is omitted the section (comment) is used. + +### twist + +Send a message via [Twist](https://twist.com). + +- `title`: The title of the message. Default: `Notice` +- `url`: Required. The URL to which the message should be sent. + +```ini +[send some feedback] +twist: "This is a message." +url: https://subdomain.twist.com/path/to/your/integration +``` + +```yaml +- send some feedback: + twist: "This is a message." + url: https://subdomain.twist.com/path/to/your/integration +``` + +!!! note + + As with Slack, you could easily define a variable for the Twist URL and set ``url: {{ twist_url }}``. See [variables](../config/variables.md). diff --git a/help/docs/commands/mysql.md b/help/docs/commands/mysql.md new file mode 100644 index 0000000..bd539d7 --- /dev/null +++ b/help/docs/commands/mysql.md @@ -0,0 +1,64 @@ +# MySQL + +Summary: Work with MySQL (and Maria) databases. + +## Common Options + +- `admin_pass`: The password off the admin-authorized user. +- `admin_user`: The user name of the admin-authorized user. Default: `root` +- `host`: The host name. Default: `localhost` +- `port`: The TCP port. Default: `3306` + +## Available Commands + +### mysql.create + +Create a database. Argument is the database name. + +- `owner`: The user name that owns the database. + +```ini +[create the database] +mysql.create: database_name +``` + +### mysql.drop + +Drop a database. Argument is the database name. + +### mysql.dump + +Dump the database schema. Argument is the database name. + +- `path`: The path to the dump file. Default: `dump.sql` + +### mysql.exec + +Execute an SQL statement. Argument is the SQL statement. + +- `database`: The name of the database where the statement will be executed. Default: `default` + +### mysql.exists + +Determine if a database exists. Argument is the database name. + +### mysql.grant + +Grant privileges to a user. Argument is the privileges to be granted. + +- `database`: The database name where privileges are granted. +- `user`: The user name for which the privileges are provided. + +### mysql.user.create + +Create a user. Argument is the user name. + +- `password`: The user's password. + +### mysql.user.drop + +Remove a user. Argument is the user name. + +### mysql.user.exists + +Determine if a user exists. Argument is the user name. diff --git a/help/docs/commands/pgsql.md b/help/docs/commands/pgsql.md new file mode 100644 index 0000000..4f9e423 --- /dev/null +++ b/help/docs/commands/pgsql.md @@ -0,0 +1,57 @@ +# PostgreSQL + +Summary: Work with Postgres databases. + +## Common Options + +- `admin_pass`: The password off the admin-authorized user. +- `admin_user`: The user name of the admin-authorized user. Default: `postgres` +- `host`: The host name. Default: `localhost` +- `port`: The TCP port. Default: `5432` + +## Available Commands + +### pgsql.create + +Create a database. Argument is the database name. + +- `owner`: The user name that owns the database. + +```ini +[create the database] +pgsql.create: database_name +``` + +### pgsql.drop + +Drop a database. Argument is the database name. + +### pgsql.dump + +Dump the database schema. Argument is the database name. + +- `path`: The path to the dump file. Default: `dump.sql` + +### pgsql.exec + +Execute an SQL statement. Argument is the SQL statement. + +- `database`: The name of the database where the statement will be executed. Default: `default` + +### pgsql.exists + +Determine if a database exists. Argument is the database name. + +### pgsql.user.create + +Create a user. Argument is the user name. + +- `password`: The user's password. + +### pgsql.user.drop + +Remove a user. Argument is the user name. + +### pgsql.user.exists + +Determine if a user exists. Argument is the user name. diff --git a/help/docs/commands/php.md b/help/docs/commands/php.md new file mode 100644 index 0000000..12c5a6b --- /dev/null +++ b/help/docs/commands/php.md @@ -0,0 +1,9 @@ +# PHP + +Summary: Work with PHP. + +## Available Commands + +### module + +Enable a PHP module. Argument is the module name. diff --git a/help/docs/commands/posix.md b/help/docs/commands/posix.md new file mode 100644 index 0000000..b08419d --- /dev/null +++ b/help/docs/commands/posix.md @@ -0,0 +1,148 @@ +# POSIX + +Summary: Work with common POSIX-compliant commands.. + +## Available Commands + +### append + +Append content to a file. Argument is the file name. + +- `content`: The content to be appended. + +### archive + +Create an archive (tarball). Argument is the target file or directory. + +- `absolute`: Don't strip leading slashes from file names. +- `view`: View the progress. +- `exclude`: Exclude file name patterns. +- `strip`: Strip component paths to the given depth (integer). +- `to`: The path to where the archive will be created. + +### copy + +Copy a file or directory. First argument is the target file/directory. Second argument is the destination. + +- `overwrite`: Overwrite an existing target. +- `recursive`: Copy directories recursively. + +### dir + +Create a directory. Argument is the path. + +- `group`: Set the group to the given group name. +- `mode`: Set the mode on the path. +- `owner`: Set the owner to the given owner name. +- `recursive`: Create the full path even if intermediate directories do not exist. + +### extract + +Extract an archive (tarball). Argument is the path to the archive file. + +- `absolute`: Strip leading slashes from file names. +- `view`: View the progress. +- `exclude`: Exclude file name patterns. +- `strip`: Strip component paths to the given depth (integer). +- `to`: The path to where the archive will be extracted. Defaults to the current working directory. + +### file + +Create a file. Argument is the path. + +- `content`: The content of the file. Otherwise, an empty file is created. +- `group`: Set the group to the given group name. +- `mode`: Set the mode on the path. +- `owner`: Set the owner to the given owner name. + +### link + +Create a symlink. First argument is the target. Second argument is the destination. + +- `force`: Force creation of the link. + +### move + +Move a file or directory. First argument is the target. Second argument is the desitnation. + +### perms + +Set permissions on a file or directory. Argument is the path. + +- `group`: Set the group to the given group name. +- `mode`: Set the mode on the path. +- `owner`: Set the owner to the given owner name. +- `recursive`: Apply permission recursively (directories only). + +### push + +Push (rsync) a path to a remote server. First argument is the local path. Second argument is the remote path. + +- `delete`: Delete existing files/directories. +- `host`: The host name. Required. +- `key_file`: Use the given SSL (private) key. Required. +- `links`: Copy symlinks. +- `exclude`: Exclude patterns from the given (local) file. +- `port`: The TCP port on the host. Default: `22` +- `recursive`: Operate recursively on directories. +- `user`: The user name. Required. + +### remove + +Remove a file or directory. Argument is the path. + +- `force`: Force the removal. +- `recursive`: Remove (directories) rescurisvely. + +### rename + +Rename a file or directory. First argument is the target. Second argument is the destination. + +### replace + +Replace something in a file. First argument is the path. + +- `backup`: Create a backup. +- `delimiiter`: The sed delimiter. Default: `/` +- `find`: The text to be found. Required. +- `sub`: The text to be replaced. Required. + +### scopy + +Copy a file to a remote server. First argument is the local file name. Second argument is the remote destination. + +- `key_file`: The private key file to use for the connection. +- `host`: The host name. Required. +- `port`: The TCP port. Default: `22` +- `user`: The user name. Required. + +### ssl + +Use Let's Encrypt (certbot) to acquire an SSL certificate. Argument is the domain name. + +- `email`: The email address for "agree tos". Default: `webmaster@domain_name` +- `webroot`: The webroot to use. Default: `/var/www/maint/www` + +### sync + +Sync (rsync) local files and directories. First argument is the target. Second argument is the destination. + +- `delete`: Delete existing files/directories. +- `links`: Copy symlinks. +- `exclude`: Exclude patterns from the given (local) file. +- `recursive`: Operate recursively on directories. + +### touch + +Touch a file, whether it exists or not. Argument is the path. + +### wait + +Wait for n number of seconds before continuing. Argument is the number of seconds. + +### write + +Write to a file. Argument is the path. + +- `content`: The content to write to the file. Replaces existing content. + diff --git a/help/docs/commands/python.md b/help/docs/commands/python.md new file mode 100644 index 0000000..2f94e5f --- /dev/null +++ b/help/docs/commands/python.md @@ -0,0 +1,20 @@ +# Python + +Summary: Work with Python. + +## Available Commands + +### pip + +Use the pip command. Argument is the package name. + +- `op`: The operation; `install` (the default), `remove`, or `updgrade`. +- `venv`: The name of the virtual environment to use. + +### pip3 + +Use Python3 pip. See pip above. + +### virtualenv + +Create a python virtual environment. Argument is the environment name. diff --git a/help/docs/config/command-file.md b/help/docs/config/command-file.md new file mode 100644 index 0000000..299ddc0 --- /dev/null +++ b/help/docs/config/command-file.md @@ -0,0 +1,208 @@ +# Command File + +A command file contains the metadata about the commands to be generated. INI and YAML formats are supported. + +In an INI file, each section is a command. With YAML, each top-level list item is a command. + +## The Comment/Description + +With INI files, the section name is the command comment. + +```ini +[this becomes the command comment] +; ... +``` + +With YAML, each command is a list item and the item name becomes the command comment: + +```yaml +- this becomes the command comment: + # ... +``` + +## First Option and Arguments + +The first variable in the section is the command name. It's value contains the required arguments. + +```ini +[restart the postfix service] +restart: postfix +``` + +```yaml +- restart the postfix service: + restart: postfix +``` + +## Formatting Notes + +With both INI and YAML files, the formatting rules are: + +- The first part of each command is the INI section or YAML item and is used as the comment. +- The command name *must* be the *first* option in the section. +- The arguments for the command appear as the value of the first option in the section. Arguments are separated by a + space. +- Arguments that should be treated as a single value should be enclosed in double quotes. +- `yes` and `no` are interpreted as boolean values. `maybe` is interpreted as `None`. +- List values, where required, are separated by commas when appearing in INI files, but are a `[standard, list, of, values]` in a YAML file. + +## Additional Attributes + +Additional variables in the section are generally optional parameters that inform or control how the command should be executed and are sometimes used to add switches to the statement. + +!!! warning + + This is not always the case, so consult the documentation for the command in question, because some parameters that appear after the first line are actually required. + +## Common Attributes + +A number of common options are recognized. Some of these have no bearing on statement generation but may be used for filtering. Others may be optionally included, and a few may only be used programmatically. + +### cd + +The `cd` option sets the directory (path) from which the statement should be executed. It is included by default when the statement is generated, but may be suppressed using `cd=False`. + +```ini +[create a python virtual environment] +virtualenv: python +cd: /path/to/project +``` + +### comment + +The comment comes from the section name (INI) or list name (YAML). It is included by default when the statement is generated, by may be suppressed using `include_comment=False`. + +```ini +[this becomes the comment] +; ... +``` + +```yaml +- this becomes the comment: + # ... +``` + +### env + +The `env` option indicates the target environment (or environments) in which the statement should run. This is not used in command generation, but may be used for filtering. + +```yaml +- set up the database: + pgsql.create: example_com + env: [staging, live] +``` + +This option may be given as `environments`, `environs`, `envs`, or simply `env`. It may be a list or CSV string. + +### prefix + +The `prefix` option is used to define a statement to be executed before the main statement is executed. + +```ini +[migrate the database] +django: migrate +cd: /path/to/project/source +prefix: source ../python/bin/activate +``` + +### register + +`register` defines the name of a variable to which the result of the statement should be saved. It is included by default when the statement is generated, but may be suppressed using `include_register=False`. + +```yaml +- check apache configuration: + apache: test + register: apache_ok +``` + +### shell + +The `shell` defines the shell to be used for command execution. It is not used for statement generation, but may be used programmatically -- for example, with Python's subprocess module. Some commands (such as Django management commands) need a shell to be explicitly defined. + +```ini +[run django checks] +django: check +cd: /path/to/project/source +prefix: source ../python/bin/activate +shell: /bin/bash +``` + +!!! note + + As this option is intended for programmatic use, it would be better to define a default shell for all command execution and use this option only when the default should be overridden. + +### stop + +A `yes` indicates processing should stop if the statement fails to execute with success. It is included by default when the statement is generated, but may be suppressed. Additionally, when [register](#register) is defined, this option will use the result of the command to determine success. This option is also useful for programmatic execution. + +```yaml +- check apache configuration: + apache: test + register: apache_ok + stop: yes +``` + +!!! warning + + Some commands do not provide an zero or non-zero exit code on success or failure. You should verify that the `stop` will actually be used. + +### sudo + +The `sudo` option may be defined as `yes` or a username. This will cause the statement to be generated with sudo. + +```ini +[install apache] +install: apache2 +sudo: yes +``` + +!!! note + + When present, sudo is always generated as part of the statement. For programmatic use, it may be better to control how and when sudo is applied using some other mechanism. If sudo should be used for all statements, it can be passed as a global option. + +### tags + +`tags` is a comma separated list (INI) or list (YAML) of tag names. These may be used for filtering. + +```yaml +- install apache: + install: apache2 + tags: [apache, web] + +- enable wsgi: + apache.enable: mod_wsgi + tags: [apache, web] + +- restart apache: + apache.restart: + tags: [apache, web] + +- run django checks: + django: check + tags: [django, python] + +- apply database migrations: + django: migrate + tags: [django, python] +``` + +## Ad Hoc Options + +Options that are not recognized as common or as part of those specific to a command are still processed by the loader. This makes it possible to define your own options based on the needs of a given implementation. + +For example, suppose you are implementing a deployment system where some commands should run locally, but most should run on the remote server. + +```ini +[run tests to validate the system] +run: make tests +local: yes +stop: yes + +[install apache] +install: apache2 +remote: yes + +; and so on ... +``` + +This will be of no use as a generated script since the generator does not know about `local` and `remote`, but these could be used programmatically to control whether Python subprocess or an SSH client is invoked. diff --git a/help/docs/config/variables.md b/help/docs/config/variables.md new file mode 100644 index 0000000..03480b0 --- /dev/null +++ b/help/docs/config/variables.md @@ -0,0 +1,79 @@ +# Variables File + +A variables file contains variable definitions that may be used as the context for parsing a [command file](command-file.md) *before* the actual commands are generated. + +Unlike a command file, the INI format is the only supported format for a variables file. + +## The Variable Name + +The variable name is defined in the section: + +```ini +[domain_name] +value: example.com +``` + +## The Variable Value + +As seen in the example above, the value is defined by simply adding a variable parameter: + +```ini +[domain_name] +value: example.com + +[database_host] +value: db1.example.com +``` + +!!! note + + This is the minimum definition for all variables. + +## Defining An Environment + +You may define an environment for any given variable that may be used for filtering variables. This is done by adding the environment name to the variable name: + +```ini +[database_host:development] +comment: Local host used in development. +value: localhost + +[database_host:live] +value: db1.example.com +``` + +In this way, variables of the same name may be supported across different deployment environments. + +## Adding Comments + +As demonstrated in the example above, you may comment on a variable by adding a `comment:` attribute to the section. + +## Defining Tags + +Tags may be defined for any variable as a comma separated list. This is useful for filtering. + +```ini +[database_host:development] +value: localhost +tags: database + +[database_host:live] +value: db1.example.com +tags: database + +[domain_name] +value: example.app +tags: application +``` + +## Other Attributes + +Any other variable defined in the section is dynamically available. + +```ini +[domain_name] +value: example.app +other: test +``` + +The value of `other` is `test`. diff --git a/help/docs/index.md b/help/docs/index.md new file mode 100644 index 0000000..0e9f4ad --- /dev/null +++ b/help/docs/index.md @@ -0,0 +1,58 @@ +# Python Script Tease + +## Overview + +Script Tease is a library and command line tool for generating Bash commands programmatically and (especially) using configuration files. + +The primary focus (and limit) is to convert plain text instructions (in INI or YAML format) into valid command line statements for a given platform. It does *not* provide support for executing those statements. + +## Concepts + +### Command Generation + +Script Tease may be used in two (2) ways: + +1. Using the library to programmatically define commands and export them as command line statements. +2. Using the `tease` command to generate commands from a configuration file. See [command file configuration](config/command-file.md). + +This documentation focuses on the second method, but the developer docs may be used in your own implementation. + +### Self-Documenting + +The format of INI and YAML files is self-documenting. The command comment is this section (INI) or start of a list item (YAML). This ensures that all commands have a basic description of their purpose or intent. + +### Snippets + +An *snippet* is simply a tokenized command that may be customized based on the instructions found in a command file. Related snippets are collected into groups and then merged into a larger set that define the capabilities of a specific operating system. + +!!! note + At present, the only fully defined operating systems are for Cent OS and Ubuntu. + +Snippets are defined in Python dictionaries. These include a "canonical" command name as the key and either a string or list which define the command. In both cases, the contents are parsed as Jinja templates. There are various approaches to evaluating a snippet. + +First: The snippet is a simple mapping of command name and command snippet. This is easy. Find the command name in the dictionary, and we have the snippet to be used. For example the `append` command in the `posix` dictionary. + +Second: The snippet is a mapping of command name and a list of snippets to be combined. Find the command name in the dictionary, and iterate through the snippets. For example, many of the commands in the `posix` dictionary takes this form. Command identification is the same as the first condition. + +Third: The command is a mapping to informal sub-commands. Examples include `apache` and `system` in the `ubuntu` dictionary. There are a couple of ways to handle this in the config file: + +- Use the outer command as the command with the inner command as the first (and perhaps only) argument. For example `apache: reload` or `system: upgrade`. +- Use a "dotted path" to find the command. For example: `apache.reload: (implicity True)` or `system.upgrade: (implicitly True)`. Or `apache.enable_site: example.com`. + +The first approach complicates things when detecting actual sub-commands (below). Script Tease supports both of these approaches. + +Fourth: The command also expects a sub-command. In some cases, the sub-command may be implicit, like `pip install`. In other cases, a number of sub-commands may be pre-defined, but ad hoc sub-commands should also be supported as with Django commands. + +Fifth: Builds upon the third and fourth conditions where the commands have lots of options, some of which may be defined at runtime. Postgres and MySQL may use be presented as informal sub-commands, but there are lots of options and challenges in building the final command. Django management commands have a number of standard options, specific options, and must also support ad hoc commands. + +## Terms and Definitions + +command +: When used in Script Tease documentation, this is a command instance which contains the properties and parameters for a command line statement. + +statement +: A specific statement (string) to be executed. A *statement* is contained within a *command*. + +## License + +Python Script Tease is released under the BSD 3 clause license. diff --git a/help/docs/profiles/centos.md b/help/docs/profiles/centos.md new file mode 100644 index 0000000..639355a --- /dev/null +++ b/help/docs/profiles/centos.md @@ -0,0 +1,124 @@ +# CentOS + +## Available Commands + +The `centos` profile incorporates commands from [Django](../commands/django.md), [messages](../commands/messages.md), [MySQL](../commands/mysql.md), [PHP](../commands/php.md), [POSIX](../commands/posix.md), [Postgres](../commands/pgsql.md), and [Python](../commands/python.md). + +### apache + +Work with Apache. + +- `apache.disable_module: module_name` (not supported) +- `apache.disable_site: site_name` (not supported) +- `apache.enable_module: module_name` (not supported) +- `apache.enable_site: site_name` (not supported) +- `apache.reload` +- `apache.restart` +- `apache.start` +- `apache.stop` +- `apache.test` + +### install + +Install a system package. + +```ini +[install apache] +install: apache2 +``` + +### reload + +Reload a service. + +```ini +[reload postgres] +reload: postgresql +``` + +### restart + +Restart a service: + +```ini +[restart postgres] +restart: postgresql +``` + +### run + +Run any shell command. + +```ini +[run a useless listing command] +run: "ls -ls" +``` + +Note that commands with arguments will need to be in quotes. + +### start + +Start a service: + +```ini +[start postgres] +start: postgresql +``` + +### stop + +Stop a service: + +```ini +[stop postgres] +stop: postgresql +``` + +### system + +With with the system. + +- `system.reboot` +- `system.update` +- `system.upgrade` + +### uninstall + +Uninstall a package. + +```ini +[remove libxyz development package] +uninstall: libxyz-dev +``` + +### upgrade + +Upgrade a package. + +```ini +[upgrade libxyz development package] +upgrade: libxyz-dev +``` + +### user + +Create a user: + +- `groups`: A comma separated list of groups to which the user should be added. +- `home`: The user's home directory. +- `login`: The shell to use. +- `system`: Create as a system user. + +```ini +[create the deploy user] +user.add: deploy +groups: www-data +home: /var/www +``` + +Remove a user: + +```ini +[remove bob] +user.remove: bob +``` diff --git a/help/docs/profiles/ubuntu.md b/help/docs/profiles/ubuntu.md new file mode 100644 index 0000000..299313f --- /dev/null +++ b/help/docs/profiles/ubuntu.md @@ -0,0 +1,124 @@ +# Ubuntu + +## Available Commands + +The `ubuntu` profile incorporates commands from [Django](../commands/django.md), [messages](../commands/messages.md), [MySQL](../commands/mysql.md), [PHP](../commands/php.md), [POSIX](../commands/posix.md), [Postgres](../commands/pgsql.md), and [Python](../commands/python.md). + +### apache + +Work with Apache. + +- `apache.disable_module: module_name` +- `apache.disable_site: site_name` +- `apache.enable_module: module_name` +- `apache.enable_site: site_name` +- `apache.reload` +- `apache.restart` +- `apache.start` +- `apache.stop` +- `apache.test` + +### install + +Install a system package. + +```ini +[install apache] +install: apache2 +``` + +### reload + +Reload a service. + +```ini +[reload postgres] +reload: postgresql +``` + +### restart + +Restart a service: + +```ini +[restart postgres] +restart: postgresql +``` + +### run + +Run any shell command. + +```ini +[run a useless listing command] +run: "ls -ls" +``` + +Note that commands with arguments will need to be in quotes. + +### start + +Start a service: + +```ini +[start postgres] +start: postgresql +``` + +### stop + +Stop a service: + +```ini +[stop postgres] +stop: postgresql +``` + +### system + +With with the system. + +- `system.reboot` +- `system.update` +- `system.upgrade` + +### uninstall + +Uninstall a package. + +```ini +[remove libxyz development package] +uninstall: libxyz-dev +``` + +### upgrade + +Upgrade a package. + +```ini +[upgrade libxyz development package] +upgrade: libxyz-dev +``` + +### user + +Create a user: + +- `groups`: A comma separated list of groups to which the user should be added. +- `home`: The user's home directory. +- `login`: The shell to use. +- `system`: Create as a system user. + +```ini +[create the deploy user] +user.add: deploy +groups: www-data +home: /var/www +``` + +Remove a user: + +```ini +[remove bob] +user.remove: bob +``` diff --git a/help/mkdocs.yml b/help/mkdocs.yml new file mode 100644 index 0000000..3a906e2 --- /dev/null +++ b/help/mkdocs.yml @@ -0,0 +1,26 @@ +site_name: Script Tease +copyright: Copyright © Pleasant Tents, LLC. All rights reserved. +markdown_extensions: + - toc: + permalink: True + - admonition + - attr_list + - def_list +nav: + - Home: index.md + - Configuration: + - Command File: config/command-file.md + - Variables: config/variables.md + - Commands: + - Django: commands/django.md + - Messages: commands/messages.md + - MySQL: commands/mysql.md + - PHP: commands/php.md + - Postgres: commands/pgsql.md + - POSIX: commands/posix.md + - Python: commands/python.md + - Profiles: + - Ubuntu: profiles/ubuntu.md +# - Developer Reference: /developers/ +repo_name: GitLab +repo_url: https://git.sixgunsoftware.com/python-scripttease diff --git a/sandbox/cli.py b/sandbox/cli.py index 668c564..df5d117 100755 --- a/sandbox/cli.py +++ b/sandbox/cli.py @@ -1,17 +1,17 @@ #! /usr/bin/env python from argparse import ArgumentParser, RawDescriptionHelpFormatter -from commonkit import highlight_code, smart_cast +from commonkit import highlight_code, indent, smart_cast from commonkit.logging import LoggingHelper from commonkit.shell import EXIT +from markdown import markdown import sys sys.path.insert(0, "../") from scripttease.constants import LOGGER_NAME -from scripttease.lib.contexts import load_variables, Context -from scripttease.lib.loaders.ini import INILoader -from scripttease.lib.loaders.yaml import YMLLoader +from scripttease.lib.contexts import Context +from scripttease.lib.loaders import load_variables, INILoader, YMLLoader from scripttease.version import DATE as VERSION_DATE, VERSION DEBUG = 10 @@ -59,10 +59,11 @@ This command is used to parse configuration files and output the commands. ) parser.add_argument( - "-d", - "--docs", - action="store_true", - dest="docs_enabled", + "-d=", + "--docs=", + choices=["html", "markdown", "plain", "rst"], + # default="markdown", + dest="docs", help="Output documentation instead of code." ) @@ -128,8 +129,6 @@ This command is used to parse configuration files and output the commands. help="Load variables from a file." ) - - # Access to the version number requires special consideration, especially # when using sub parsers. The Python 3.3 behavior is different. See this # answer: http://stackoverflow.com/questions/8521612/argparse-optional-subparser-for-version @@ -181,7 +180,7 @@ This command is used to parse configuration files and output the commands. filters[key].append(value) - # Handle options. + # Handle global command options. options = dict() if args.options: for token in args.options: @@ -217,11 +216,104 @@ This command is used to parse configuration files and output the commands. exit(EXIT.ERROR) # Generate output. - if args.docs_enabled: - pass + if args.docs: + output = list() + for snippet in loader.get_snippets(): + if snippet is None: + continue + + if snippet.name == "explain": + if snippet.header: + output.append("## %s" % snippet.name.title()) + output.append("") + + output.append(snippet.args[0]) + output.append("") + elif snippet.name == "screenshot": + if args.docs == "html": + b = list() + b.append('") + output.append("") + elif args.docs == "plain": + output.append(snippet.args[0]) + output.append("") + elif args.docs == "rst": + output.append(".. figure:: %s" % snippet.args[0]) + + if snippet.caption: + output.append(indent(":alt: %s" % snippet.caption or snippet.comment)) + + if snippet.height: + output.append(indent(":height: %s" % snippet.height)) + + if snippet.width: + output.append(indent(":width: %s" % snippet.width)) + + output.append("") + else: + output.append("![%s](%s)" % (snippet.caption or snippet.comment, snippet.args[0])) + output.append("") + elif snippet.name == "template": + if args.docs == "plain": + output.append("+++") + output.append(snippet.get_content()) + output.append("+++") + elif args.docs == "rst": + output.append(".. code-block:: %s" % snippet.get_target_language()) + output.append("") + output.append(indent(snippet.get_content())) + output.append("") + else: + output.append("```%s" % snippet.get_target_language()) + output.append(snippet.get_content()) + output.append("```") + else: + statement = snippet.get_statement(include_comment=False, include_register=False, include_stop=False) + if statement is not None: + line = snippet.comment.replace("#", "") + output.append("%s:" % line.capitalize()) + output.append("") + if args.docs == "plain": + output.append("---") + output.append(statement) + output.append("---") + output.append("") + elif args.docs == "rst": + output.append(".. code-block:: bash") + output.append("") + output.append(indent(statement)) + output.append("") + else: + output.append("```bash") + output.append(statement) + output.append("```") + output.append("") + + if args.docs == "html": + print(markdown("\n".join(output), extensions=['fenced_code'])) + else: + print("\n".join(output)) else: commands = list() for snippet in loader.get_snippets(): + # Explanations and screenshots don't produce usable statements but may be added as comments. + if snippet.name in ("explain", "screenshot"): + commands.append("# %s" % snippet.args[0]) + commands.append("") + continue + statement = snippet.get_statement() if statement is not None: commands.append(statement) diff --git a/scripttease/constants.py b/scripttease/constants.py index 26d0a12..ae45aae 100644 --- a/scripttease/constants.py +++ b/scripttease/constants.py @@ -1 +1,12 @@ +EXCLUDED_KWARGS = [ + "cd", + "comment", + "environments", + "prefix", + "register", + "shell", + "stop", + "tags", +] + LOGGER_NAME = "script-tease" diff --git a/scripttease/data/inventory/nextcloud/steps.ini b/scripttease/data/inventory/nextcloud/steps.ini index a569658..ebe04a4 100644 --- a/scripttease/data/inventory/nextcloud/steps.ini +++ b/scripttease/data/inventory/nextcloud/steps.ini @@ -50,38 +50,9 @@ apache.enable_module: rewrite [enable SSL engine] apache.enable_module: ssl -[enable php ctype] -run: "phpenmod ctype" - -[enable php curl] -run: "phpenmod curl" - -[enable php dom] -run: "phpenmod dom" - -[enable php GD] -run: "phpenmod gd" - -[enable php JSON] -run: "phpenmod json" - -[enable php PGSQL] -run: "phpenmod pdo_pgsql" - -[enable php SimpleXML] -run: "phpenmod simplexml" - -[enable php posix] -run: "phpenmod posix" - -[enable php XMLReader] -run: "phpenmod xmlreader" - -[enable php XMLWriter] -run: "phpenmod xmlwriter" - -[enable php zip] -run: "phpenmod zip" +[enable php modules] +php.module: $item +items: ctype, curl, dom, gd, json, pdo_pgsql, posix, simplexml, xmlreader, xmlwriter, zip ;PHP module libxml (Linux package libxml2 must be >=2.7.0) ;php -i | grep -i libxml diff --git a/scripttease/data/inventory/radicale/steps.ini b/scripttease/data/inventory/radicale/steps.ini index d4f5e2e..041c1de 100644 --- a/scripttease/data/inventory/radicale/steps.ini +++ b/scripttease/data/inventory/radicale/steps.ini @@ -1,12 +1,23 @@ +[introduction] +explain: "In this tutorial, we are going to install Radicale." +header: yes + [make sure a maintenance root exists] mkdir: /var/www/maint/www group: www-data owner: www-data recursive: yes +[about maintenance root] +explain: "The maintenance root is used to register an SSL certificate (below) before the site is completed and (later) after the site is live." + [install radicale] pip3: radicale +;[install radicale screenshot] +;screenshot: images/install.png +;caption: Radical Installed + [create radicale configuration directory] mkdir: /etc/radicale/config owner: radicale @@ -24,6 +35,7 @@ system: yes [create the systemd service file for radicale] template: radicale.service /etc/systemd/system/radicale.service +lang: ini [start the radicale service] start: radicale diff --git a/scripttease/lib/contexts.py b/scripttease/lib/contexts.py index 116ec7c..632d2c7 100644 --- a/scripttease/lib/contexts.py +++ b/scripttease/lib/contexts.py @@ -1,9 +1,6 @@ # Imports -from commonkit import smart_cast -from configparser import ParsingError, RawConfigParser import logging -import os log = logging.getLogger(__name__) @@ -14,44 +11,6 @@ __all__ = ( "Variable", ) -# Functions - - -def load_variables(path): - """Load variables from an INI file. - - :param path: The path to the INI file. - :type path: str - - :rtype: list[scripttease.lib.contexts.Variable] - - """ - if not os.path.exists(path): - log.warning("Variables file does not exist: %s" % path) - return list() - - ini = RawConfigParser() - try: - ini.read(path) - except ParsingError as e: - log.warning("Failed to parse %s variables file: %s" % (path, str(e))) - return list() - - variables = list() - for variable_name in ini.sections(): - _value = None - kwargs = dict() - for key, value in ini.items(variable_name): - if key == "value": - _value = smart_cast(value) - continue - - kwargs[key] = smart_cast(value) - - variables.append(Variable(variable_name, _value, **kwargs)) - - return variables - # Classes @@ -85,6 +44,15 @@ class Context(object): return v + def append(self, variable): + """Append a variable to the context. + + :param variable: The variable to be added to the context. + :type variable: scripttease.lib.contexts.Variable + + """ + self.variables[variable.name] = variable + def mapping(self): """Get the context as a dictionary. @@ -98,11 +66,10 @@ class Context(object): return d - class Variable(object): """An individual variable.""" - def __init__(self, name, value, **kwargs): + def __init__(self, name, value, environment=None, **kwargs): """Initialize a variable. :param name: The name of the variable. @@ -110,13 +77,18 @@ class Variable(object): :param value: The value of the variable. + :param environment: The environment in which the variable is used. + :type environment: str + kwargs are available as dynamic attributes. """ - self.attributes = kwargs + self.environment = environment self.name = name self.value = value + self.attributes = kwargs + def __getattr__(self, item): return self.attributes.get(item) diff --git a/scripttease/lib/loaders/__init__.py b/scripttease/lib/loaders/__init__.py index 4a0d831..527b4ef 100644 --- a/scripttease/lib/loaders/__init__.py +++ b/scripttease/lib/loaders/__init__.py @@ -1,3 +1,6 @@ """ The job of a loader is to collect commands and their arguments from a text file. -""" \ No newline at end of file +""" +from .base import filter_snippets, load_variables +from .ini import INILoader +from .yaml import YMLLoader diff --git a/scripttease/lib/loaders/base.py b/scripttease/lib/loaders/base.py index 0d067ca..665a665 100644 --- a/scripttease/lib/loaders/base.py +++ b/scripttease/lib/loaders/base.py @@ -1,21 +1,112 @@ # Imports -from commonkit import parse_jinja_string, parse_jinja_template, pick, read_file, smart_cast, split_csv, File +from commonkit import any_list_item, parse_jinja_string, parse_jinja_template, pick, read_file, smart_cast, split_csv, \ + File +from configparser import ParsingError, RawConfigParser from jinja2.exceptions import TemplateError, TemplateNotFound import logging import os +from ...constants import EXCLUDED_KWARGS +from ..contexts import Variable from ..snippets.mappings import MAPPINGS log = logging.getLogger(__name__) # Exports + __all__ = ( + "filter_snippets", + "load_variables", "BaseLoader", "Snippet", "Sudo", "Template", ) +# Functions + + +def filter_snippets(snippets, environments=None, tags=None): + """Filter snippets based on the given criteria. + + :param snippets: The snippets to be filtered. + :type snippets: list[scripttease.lib.loaders.base.Snippet] + + :param environments: Environment names to be matched. + :type environments: list[str] + + :param tags: Tag names to be matched. + :type tags: list[str] + + """ + filtered = list() + for snippet in snippets: + if environments is not None and len(snippet.environments) > 0: + if not any_list_item(environments, snippet.environments): + continue + + if tags is not None: + if not any_list_item(tags, snippet.tags): + continue + + filtered.append(snippet) + + return filtered + + +def load_variables(path, env=None): + """Load variables from an INI file. + + :param path: The path to the INI file. + :type path: str + + :param env: The environment name of variables to return. + :type env: str + + :rtype: list[scripttease.lib.contexts.Variable] + + """ + if not os.path.exists(path): + log.warning("Variables file does not exist: %s" % path) + return list() + + ini = RawConfigParser() + try: + ini.read(path) + except ParsingError as e: + log.warning("Failed to parse %s variables file: %s" % (path, str(e))) + return list() + + variables = list() + for variable_name in ini.sections(): + if ":" in variable_name: + variable_name, _environment = variable_name.split(":") + else: + _environment = None + variable_name = variable_name + + kwargs = { + 'environment': _environment, + } + _value = None + for key, value in ini.items(variable_name): + if key == "value": + _value = smart_cast(value) + continue + + kwargs[key] = smart_cast(value) + + variables.append(Variable(variable_name, _value, **kwargs)) + + if env is not None: + filtered_variables = list() + for var in variables: + if var.environment and var.environment == env or var.environment is None: + filtered_variables.append(var) + + return filtered_variables + + return variables # Classes @@ -24,7 +115,8 @@ __all__ = ( class BaseLoader(File): """Base class for loading a command file.""" - def __init__(self, path, context=None, locations=None, mappings=None, profile="ubuntu", **kwargs): + def __init__(self, path, context=None, excluded_kwargs=None, locations=None, mappings=None, profile="ubuntu", + **kwargs): """Initialize the loader. :param path: The path to the command file. @@ -34,6 +126,13 @@ class BaseLoader(File): converted to a ``dict`` when passed to a Snippet or Template. :type context: scripttease.lib.contexts.Context + :param excluded_kwargs: For commands that support ad hoc sub-commands (like Django), this is a list of keyword + argument names that must be removed. Defaults to the names of common command attributes. + If your implementation requires custom but otherwise standard command attributes, you'll + need to import the ``EXCLUDED_KWARGS`` constant and add your attribute names before + passing it to the loader. + :type excluded_kwargs: list[str] + :param locations: A list of paths where templates and other external files may be found. The ``templates/`` directory in which the command file exists is added automatically. :type locations: list[str] @@ -50,6 +149,7 @@ class BaseLoader(File): """ self.context = context + self.excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS self.is_loaded = False self.locations = locations or list() self.mappings = mappings or MAPPINGS @@ -275,7 +375,7 @@ class Snippet(object): """ - def __init__(self, name, args=None, content=None, context=None, kwargs=None, parser=None): + def __init__(self, name, args=None, content=None, context=None, excluded_kwargs=None, kwargs=None, parser=None): """Initialize a snippet. :param name: The canonical name of the snippet. @@ -290,6 +390,9 @@ class Snippet(object): :param context: Additional context variables used to render the command. :type context: dict + :param excluded_kwargs: See parameter description for BaseLoader. + :type excluded_kwargs: list[str] + :param kwargs: The keyword arguments found in the config file. These may be specific to the command or one of the common options. They are accessible as dynamic attributes of the Snippet instance. :type kwargs: dict @@ -299,13 +402,17 @@ class Snippet(object): """ self.args = args or list() + self.excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS self.parser = parser self.content = content self.context = context or dict() self.kwargs = kwargs or dict() self.name = name - sudo = self.kwargs.get("sudo", None) + self.environments = kwargs.pop("environments", list()) + self.tags = kwargs.pop("tags", list()) + + sudo = self.kwargs.pop("sudo", None) if isinstance(sudo, Sudo): self.sudo = sudo elif type(sudo) is str: @@ -360,7 +467,7 @@ class Snippet(object): args.append(arg.replace("$item", item)) if self.parser: - statement = self.parser(self, args=args) + statement = self.parser(self, args=args, excluded_kwargs=self.excluded_kwargs) else: statement = self._parse(args=args) @@ -392,7 +499,7 @@ class Snippet(object): a.append("%s &&" % self.prefix) if self.parser: - statement = self.parser(self) + statement = self.parser(self, excluded_kwargs=self.excluded_kwargs) else: statement = self._parse() @@ -503,16 +610,29 @@ class Sudo(object): class Template(object): PARSER_JINJA = "jinja2" + PARSER_PYTHON = "python" PARSER_SIMPLE = "simple" def __init__(self, source, target, backup=True, parser=PARSER_JINJA, **kwargs): self.backup_enabled = backup self.context = kwargs.pop("context", dict()) + self.name = "template" self.parser = parser + self.language = kwargs.pop("lang", None) self.locations = kwargs.pop("locations", list()) self.source = os.path.expanduser(source) self.target = target + sudo = kwargs.pop("sudo", None) + if isinstance(sudo, Sudo): + self.sudo = sudo + elif type(sudo) is str: + self.sudo = Sudo(enabled=True, user=sudo) + elif sudo is True: + self.sudo = Sudo(enabled=True) + else: + self.sudo = Sudo() + self.kwargs = kwargs def __getattr__(self, item): @@ -537,6 +657,10 @@ class Template(object): return content + if self.parser == self.PARSER_PYTHON: + content = read_file(template) + return content % self.context + try: return parse_jinja_template(template, self.context) except TemplateNotFound: @@ -554,7 +678,8 @@ class Template(object): # TODO: Backing up a template's target is currently specific to bash. if self.backup_enabled: - lines.append('if [[ -f "%s" ]]; then mv %s %s.b; fi;' % (self.target, self.target, self.target)) + command = "%s mv %s %s.b" % (self.sudo, self.target, self.target) + lines.append('if [[ -f "%s" ]]; then %s fi;' % (self.target, command.lstrip())) # Get the content; e.g. parse the template. content = self.get_content() @@ -563,12 +688,15 @@ class Template(object): if content.startswith("#!"): _content = content.split("\n") first_line = _content.pop(0) - lines.append('echo "%s" > %s' % (first_line, self.target)) - lines.append("cat >> %s << EOF" % self.target) + command = '%s echo "%s" > %s' % (self.sudo, first_line, self.target) + lines.append(command.lstrip()) + command = "%s cat >> %s << EOF" % (self.sudo, self.target) + lines.append(command.lstrip()) lines.append("\n".join(_content)) lines.append("EOF") else: - lines.append("cat > %s << EOF" % self.target) + command = "%s cat >> %s << EOF" % (self.sudo, self.target) + lines.append(command.lstrip()) lines.append(content) lines.append("EOF") @@ -584,6 +712,25 @@ class Template(object): return "\n".join(lines) + def get_target_language(self): + if self.language is not None: + return self.language + + if self.target.endswith(".conf"): + return "conf" + elif self.target.endswith(".ini"): + return "ini" + elif self.target.endswith(".php"): + return "php" + elif self.target.endswith(".py"): + return "python" + elif self.target.endswith(".sh"): + return "bash" + elif self.target.endswith(".yml"): + return "yaml" + else: + return "text" + def get_template(self): """Get the template path. diff --git a/scripttease/lib/snippets/django.py b/scripttease/lib/snippets/django.py index 21bf612..3d2ae8f 100644 --- a/scripttease/lib/snippets/django.py +++ b/scripttease/lib/snippets/django.py @@ -1,20 +1,10 @@ from commonkit import parse_jinja_string +from ...constants import EXCLUDED_KWARGS -DJANGO_EXCLUDED_KWARGS = [ - "cd", - "comment", - "environments", - "prefix", - "register", - "shell", - "stop", - "tags", - # "venv", # ? -] def django_command_parser(snippet, args=None, excluded_kwargs=None): - _excluded_kwargs = excluded_kwargs or DJANGO_EXCLUDED_KWARGS + _excluded_kwargs = excluded_kwargs or EXCLUDED_KWARGS # We need to remove the common options so any remaining keyword arguments are converted to switches for the # management command. diff --git a/scripttease/lib/snippets/mappings.py b/scripttease/lib/snippets/mappings.py index 9d0f922..338ee58 100644 --- a/scripttease/lib/snippets/mappings.py +++ b/scripttease/lib/snippets/mappings.py @@ -5,6 +5,7 @@ from .django import django from .messages import messages from .mysql import mysql from .pgsql import pgsql +from .php import php from .posix import posix from .python import python from .ubuntu import ubuntu @@ -56,6 +57,6 @@ def merge_dictionaries(first: dict, second: dict) -> dict: MAPPINGS = { - 'centos': merge(centos, django, messages, mysql, pgsql, posix, python), - 'ubuntu': merge(ubuntu, django, messages, mysql, pgsql, posix, python), + 'centos': merge(centos, django, messages, mysql, pgsql, php, posix, python), + 'ubuntu': merge(ubuntu, django, messages, mysql, pgsql, php, posix, python), } diff --git a/scripttease/lib/snippets/messages.py b/scripttease/lib/snippets/messages.py index 65ad7c4..f01447a 100644 --- a/scripttease/lib/snippets/messages.py +++ b/scripttease/lib/snippets/messages.py @@ -7,6 +7,8 @@ messages = { 'clear;' ], 'echo': 'echo "{{ args[0] }}"', + 'explain': None, + 'screenshot': None, 'slack': [ "curl -X POST -H 'Content-type: application/json' --data", '{"text": "{{ args[0] }}"}', diff --git a/scripttease/lib/snippets/mysql.py b/scripttease/lib/snippets/mysql.py index c841dfe..eda4fd1 100644 --- a/scripttease/lib/snippets/mysql.py +++ b/scripttease/lib/snippets/mysql.py @@ -54,7 +54,7 @@ mysql = { '{% if admin_pass %}--password="{{ admin_pass }}"{% endif %}', '--host={{ host|default("localhost") }}', '--port={{ port|default("3306") }}', - '--execute="GRANT {{ args[0] }} ON {{ database }}.* TO \'{{ user }}\'@\'{{ host|default("localhost") }}\'"' + '--execute="GRANT {{ args[0] }} ON {{ database|default("default") }}.* TO \'{{ user }}\'@\'{{ host|default("localhost") }}\'"' ], 'user': { 'create': [ diff --git a/scripttease/lib/snippets/pgsql.py b/scripttease/lib/snippets/pgsql.py index d5ce2a7..7da25e2 100644 --- a/scripttease/lib/snippets/pgsql.py +++ b/scripttease/lib/snippets/pgsql.py @@ -57,7 +57,7 @@ pgsql = { '--host={{ host|default("localhost") }}', '--port={{ port|default("5432") }}', "--column-inserts", - '--file={{ file_name|default("dump.sql") }}', + '--file={{ path|default("dump.sql") }}', "{{ args[0] }}" ], 'exec': [ diff --git a/scripttease/lib/snippets/php.py b/scripttease/lib/snippets/php.py new file mode 100644 index 0000000..867b32a --- /dev/null +++ b/scripttease/lib/snippets/php.py @@ -0,0 +1,3 @@ +php = { + 'module': "phpenmod {{ args[0] }}", +} diff --git a/scripttease/lib/snippets/ubuntu.py b/scripttease/lib/snippets/ubuntu.py index e2a65cc..9679320 100644 --- a/scripttease/lib/snippets/ubuntu.py +++ b/scripttease/lib/snippets/ubuntu.py @@ -1,9 +1,9 @@ ubuntu = { 'apache': { - 'disable': '{% if args[0].startswith("mod_") %}a2dismod{% else %}a2dissite{% endif %} {{ args[0] }}', - 'disable_module': "a2dissite {{ args[0] }}", - 'disable_site': "a2dismod {{ args[0] }}", - 'enable': '{% if args[0].startswith("mod_") %}a2enmod{% else %}a2ensite{% endif %} {{ args[0] }}', + # 'disable': '{% if args[0].startswith("mod_") %}a2dismod{% else %}a2dissite{% endif %} {{ args[0] }}', + 'disable_module': "a2dismod {{ args[0] }}", + 'disable_site': "a2dissite {{ args[0] }}", + # 'enable': '{% if args[0].startswith("mod_") %}a2enmod{% else %}a2ensite{% endif %} {{ args[0] }}', 'enable_module': "a2enmod {{ args[0] }}", 'enable_site': "a2ensite {{ args[0] }}", 'reload': "service apache2 reload", diff --git a/scripttease/version.py b/scripttease/version.py index e8a2fdc..fef3ff8 100644 --- a/scripttease/version.py +++ b/scripttease/version.py @@ -2,4 +2,4 @@ DATE = "2021-01-26" VERSION = "6.8.2" MAJOR = 6 MINOR = 8 -PATCH = 2 +PATCH = 3 diff --git a/setup.py b/setup.py index 03baa6f..297385e 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,8 @@ setup( "jinja2", "pygments", "python-commonkit", + "pyyaml", + "tabulate", ], # dependency_links=[ # "https://github.com/develmaycare/superpython",