Repository: f0rkz/pysteamcmd (language: python)

Issue:
Feature: Add steamcmd workshop_build_item option
Email request:

```
From: "Владимир Аршинов" <mrshandy@shandy-dev.ru>
To: "f0rkz@f0rkznet.net" <f0rkz@f0rkznet.net>
Subject: New functionality for pysteamcmd
Date: Sun, 22 Aug 2021 13:26:01 +0300

Hello. I am writing about https://github.com/f0rkz/pysteamcmd. I would like to see a feature for uploading content in steam workshop. This could help me a lot :)
```

Add the `workshop_build_item` option to steamcmd.
Automation: Bring in github actions to build and publish the tool

Feature: New methods of publishing packages
https://packaging.python.org/tutorials/packaging-projects/
Automation: Bring in github actions to build and publish the tool



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..4f1a141
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,34 @@
+---
+
+name: Python package
+
+on: [push]
+
+jobs:
+  build:
+
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        python-version: [3.6, 3.7, 3.8, 3.9]
+
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python ${{ matrix.python-version }}
+        uses: actions/setup-python@v2
+        with:
+          python-version: ${{ matrix.python-version }}
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install flake8 pytest
+          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+      - name: Lint with flake8
+        run: |
+          # stop the build if there are Python syntax errors or undefined names
+          flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+          # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+          flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+      # - name: Test with pytest
+      #   run: |
+      #     pytest
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..a6661d2
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,26 @@
+name: Upload Python Package
+
+on:
+  release:
+    types: [published]
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Set up Python
+        uses: actions/setup-python@v2
+        with:
+          python-version: '3.x'
+      - name: Install dependencies
+        run: |
+          python -m pip install --upgrade pip
+          pip install build
+      - name: Build package
+        run: python -m build
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
+        with:
+          user: __token__
+          password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/LICENSE b/LICENSE
index 00c5031..d633a70 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2017 f0rkz@f0rkznet.net
+Copyright (c) 2021 f0rkz@f0rkznet.net
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..4cdd3ad
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,7 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "vdf==3.4",
+    "wheel"
+]
+build-backend = "setuptools.build_meta"
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e630ebd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,1 @@
+vdf==3.4
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..9d65dea
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,24 @@
+[metadata]
+name = pysteamcmd
+version = 0.2.0
+author = f0rkz
+author_email = f0rkz@f0rkznet.net
+description = Python package to install and utilize steamcmd
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://github.com/f0rkz/pysteamcmd
+project_urls =
+    Bug Tracker = https://github.com/f0rkz/pysteamcmd/issues
+classifiers =
+    Programming Language :: Python :: 3
+    License :: MIT License
+    Operating System :: OS Independent
+
+[options]
+package_dir =
+    = src
+packages = find:
+python_requires = >=3.6
+
+[options.packages.find]
+where = src
\ No newline at end of file
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 9d2c992..0000000
--- a/setup.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from setuptools import setup
-
-setup(name='pysteamcmd',
-      version='0.1.2',
-      description='Python package to install and utilize steamcmd',
-      url='http://github.com/f0rkz/pysteamcmd',
-      author='f0rkz',
-      author_email='f0rkz@f0rkznet.net',
-      license='MIT',
-      packages=['pysteamcmd'],
-      install_requires=[],
-      zip_safe=False)
diff --git a/pysteamcmd/__init__.py b/src/pysteamcmd/__init__.py
similarity index 74%
rename from pysteamcmd/__init__.py
rename to src/pysteamcmd/__init__.py
index 71f7ffc..7666599 100644
--- a/pysteamcmd/__init__.py
+++ b/src/pysteamcmd/__init__.py
@@ -17,9 +17,12 @@
     >>> steamcmd.install()
     True
     >>> gameserver_path = os.path.join('/','home','f0rkz','mygameserver')
-    >>> steamcmd.install_gamefiles(gameid=232330, game_install_dir=gameserver_path, user='anonymous', password=None, validate=True)
+    >>> steamcmd.install_gamefiles(
+    ...    gameid=232330,
+    ...    game_install_dir=gameserver_path,
+    ...    user='anonymous',
+    ...    password=None,
+    ...    validate=True,
+    ... )
 
 """
-
-
-from .steamcmd import SteamcmdException, Steamcmd
diff --git a/pysteamcmd/steamcmd.py b/src/pysteamcmd/steamcmd.py
similarity index 70%
rename from pysteamcmd/steamcmd.py
rename to src/pysteamcmd/steamcmd.py
index a8152d1..0f048de 100644
--- a/pysteamcmd/steamcmd.py
+++ b/src/pysteamcmd/steamcmd.py
@@ -5,26 +5,21 @@
 import tarfile
 import subprocess
 
+import vdf
+
 # urllib.urlretrieve no longer exists in python 3.
 pythonVersion = sys.version_info[0]
 if pythonVersion == 3:
-	from urllib.request import urlretrieve
+    from urllib.request import urlretrieve
 elif pythonVersion == 2:
-	from urllib import urlretrieve
+    from urllib import urlretrieve
+
 
 class SteamcmdException(Exception):
     """
     Base exception for the pysteamcmd package
     """
-    def __init__(self, message=None, *args, **kwargs):
-        self.message = message
-        super(SteamcmdException, self).__init__(*args, **kwargs)
-
-    def __unicode__(self):
-        return repr(self.message)
-
-    def __str__(self):
-        return repr(self.message)
+    pass
 
 
 class Steamcmd(object):
@@ -34,8 +29,7 @@ def __init__(self, install_path):
         """
         self.install_path = install_path
         if not os.path.isdir(self.install_path):
-            raise SteamcmdException('Install path is not a directory or does not exist: '
-                                      '{}'.format(self.install_path))
+            raise SteamcmdException('Install path is not a directory or does not exist: {}'.format(self.install_path))
 
         self.platform = platform.system()
         if self.platform == 'Windows':
@@ -49,8 +43,9 @@ def __init__(self, install_path):
             self.steamcmd_exe = os.path.join(self.install_path, 'steamcmd.sh')
 
         else:
-            raise SteamcmdException('The operating system is not supported.'
-                                      'Expected Linux or Windows, received: {}'.format(self.platform))
+            raise SteamcmdException(
+                'The operating system is not supported. Expected Linux or Windows, received: {}'.format(self.platform)
+            )
 
     def _download_steamcmd(self):
         try:
@@ -69,8 +64,16 @@ def _extract_steamcmd(self):
 
         else:
             # This should never happen, but let's just throw it just in case.
-            raise SteamcmdException('The operating system is not supported.'
-                                      'Expected Linux or Windows, received: {}'.format(self.platform))
+            raise SteamcmdException(
+                'The operating system is not supported. Expected Linux or Windows, received: {}'.format(self.platform)
+            )
+
+    def _parse_vdf(self, vdf_path=None, vdf_data=None):
+        if vdf_path and os.path.isfile(vdf_path):
+            return vdf.parse(open(vdf_path))
+        if vdf_data:
+            return vdf.parse(vdf_data)
+        return None
 
     def install(self, force=False):
         """
@@ -78,7 +81,7 @@ def install(self, force=False):
         :param force: forces steamcmd install regardless of its presence
         :return:
         """
-        if not os.path.isfile(self.steamcmd_exe) or force == True:
+        if not os.path.isfile(self.steamcmd_exe) or force is True:
             # Steamcmd isn't installed. Go ahead and install it.
             try:
                 self._download_steamcmd()
@@ -150,4 +153,33 @@ def install_workshopfiles(self, gameid, workshop_id, game_install_dir, user='ano
         try:
             return subprocess.check_call(steamcmd_params)
         except subprocess.CalledProcessError:
-            raise SteamcmdException("Steamcmd was unable to run. Did you install your 32-bit libraries?")
\ No newline at end of file
+            raise SteamcmdException("Steamcmd was unable to run. Did you install your 32-bit libraries?")
+
+    def upload_workshopfiles(self, user, password, workshop_vdf_path=None, workshop_vdf=None):
+        """
+        Uploads workshop items in accordance to the workshop vdf file.
+        :param workshop_vdf_path: path to workshop vdf file.
+        :param workshop_vdf: Optional, vdf object See https://pypi.org/project/vdf/
+        :param user: steam username (required valid user)
+        :param password: steam password (required valid user password)
+        """
+        if workshop_vdf_path:
+            vdf_data = self._parse_vdf(vdf_path=workshop_vdf_path)
+        elif workshop_vdf:
+            vdf_data = self._parse_vdf_text(vdf_data=workshop_vdf)
+
+        with open("{install_path}/workshop.vdf".format(install_path=self.install_path), "w") as vdf_file:
+            vdf.dumps(vdf_data, vdf_file)
+
+        # Upload the workshop files
+        steamcmd_params = (
+            self.steamcmd_exe,
+            '+login {} {}'.format(user, password),
+            '+workshop_build_item',
+            '{install_path}/workshop.vdf'.format(install_path=self.install_path),
+            '+quit',
+        )
+        try:
+            return subprocess.check_call(steamcmd_params)
+        except subprocess.CalledProcessError:
+            raise SteamcmdException("Steamcmd was unable to run. Did you install your 32-bit libraries?")


Test patch:
diff --git a/pysteamcmd/tests/__init__.py b/src/pysteamcmd/tests/__init__.py
similarity index 100%
rename from pysteamcmd/tests/__init__.py
rename to src/pysteamcmd/tests/__init__.py