diff --git a/.gitignore b/.gitignore index 65dca71ff..d4e9b0c6e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ extensions/* **/.suo **/obj **/packages -**/pyRevitLoader/bin +**/dev/**/bin # ignore sphinx build files docs/_* @@ -33,4 +33,6 @@ __py* # Release files release/pyRevit-cache -release/pyRevit-SetupFiles \ No newline at end of file +release/pyRevit-SetupFiles +release/pyRevit\ CLI-cache +release/pyRevit\ CLI-SetupFiles \ No newline at end of file diff --git a/README_CLI.md b/README_CLI.md new file mode 100644 index 000000000..b0d1a0fa9 --- /dev/null +++ b/README_CLI.md @@ -0,0 +1,706 @@ +# pyRevit Command Line Tool Help + +##### Version 0.3.0 + +`pyrevit` is the command line tool, developed specifically to install and configure pyRevit in your production/development environment. Each section below showcases a specific set of functionality of the command line tool. + +- [pyRevit Command Line Tool Help](#pyrevit-command-line-tool-help) + - [Version 0.3.0](#version-030) + - [Getting Help](#getting-help) + - [pyrevit CLI version](#pyrevit-cli-version) + - [pyRevit Online Resources](#pyrevit-online-resources) + - [Managing pyRevit clones](#managing-pyrevit-clones) + - [Installing pyRevit](#installing-pyrevit) + - [Installing Custom Clones](#installing-custom-clones) + - [Maintaining Clones](#maintaining-clones) + - [Managing Git Clones](#managing-git-clones) + - [Updating Clones](#updating-clones) + - [Attaching pyRevit to Installed Revits](#attaching-pyrevit-to-installed-revits) + - [Managing pyRevit extensions](#managing-pyrevit-extensions) + - [Finding Extensions](#finding-extensions) + - [Installing Extensions](#installing-extensions) + - [Managing Installed Extensions](#managing-installed-extensions) + - [Updating Extensions](#updating-extensions) + - [Managing Extensions Lookup Sources](#managing-extensions-lookup-sources) + - [Getting Environment Info](#getting-environment-info) + - [Configuring pyRevit](#configuring-pyrevit) + - [Configuring Sensitive Tools](#configuring-sensitive-tools) + - [Configuring Your Own Options](#configuring-your-own-options) + - [Using Config as Seed](#using-config-as-seed) + - [Extra Revit-Related Functionality](#extra-revit-related-functionality) + - [CLI Execution of Python Scripts](#cli-execution-of-python-scripts) + - [Running a Script on Revit Models](#running-a-script-on-revit-models) + - [Clear pyRevit Cache Files](#clear-pyrevit-cache-files) + - [Logging CLI Debug Messages](#logging-cli-debug-messages) + - [Creating Shortcuts](#creating-shortcuts) + + +## Getting Help +There is a lot of commands and options available in `pyrevit`. These functionalities are grouped by their function. This document will guide you in using these commands and options based on what you're trying to achieve. See the sections below. A full list can be obtained by running: + +``` powershell +pyrevit help +pyrevit (-h | --help) + +$ pyrevit help # will take you to this page + +$ pyrevit --help # OR +$ pyrevit -h # will print help to console +``` + +### pyrevit CLI version + +To determine the version of your installed `pyrevit` cli tool: + +``` powershell +pyrevit (-V | --version) + +$ pyrevit -V # OR +$ pyrevit --version + pyrevit v0.1.5.0 +``` + +### pyRevit Online Resources + +To access a variety of online resource on pyRevit, use these commands + +``` powershell +pyrevit (blog | docs | source | youtube | support) + +$ pyrevit blog # pyRevit blog page +$ pyrevit docs # pyRevit documentation +$ pyrevit source # pyRevit source code +$ pyrevit youtube # pyRevit YouTube channel +$ pyrevit support # pyRevit suppport page for pyRevit patrons +``` + +## Managing pyRevit clones + +### Installing pyRevit + +``` powershell +pyrevit clone [--dest=] [--source=] [--branch=] [--log=] +pyrevit clone [--dest=] [--source=] [--branch=] [--log=] +``` +`pyrevit` can maintain multiple clones of pyRevit on your system. In order to do so, it needs to assign a name to each clone (``). You'll set this name when cloning pyRevit or when adding an existing clone to `pyrevit` registry. From then on you can always refer to that clone by its name. + +Let's say I want one clone of pyRevit `master` branch as my master repo; one clone of pyRevit without the full git repository (much smaller download) as my main repo; and finally one clone of the `develop` branch of pyRevit as my development repo. + +Let's create the master clone first. This will be a full git repo of the master branch. + +``` powershell +$ # master is that we're providing to pyrevit cli +$ # we're not providing any value for , therefore pyrevit cli will clone +$ # pyRevit into the default path (%APPDATA%/pyRevit) +$ pyrevit clone master +``` + +Now let's create the main clone. This one does not include the full repository. It'll be cloned from the ZIP archive provided by the Github repo: + +``` powershell +$ # we're providing the for this clone +$ # we're using the `base` deployment in this example which includes the base pyRevit tools +$ pyrevit clone main base --dest="C:\pyRevit\main" +``` + +- ``: When provided the tool installs from the ZIP archive and NOT the complete git repo. This is the preferred method and is used with the native pyRevit installer. pyRevit has multiple deployments. These deployments are defined in the [pyRevitfile](https://github.com/eirannejad/pyRevit/blob/develop/pyRevitfile) inside the pyRevit repo. Each deployment, only deploys a subset of directories. + +Now let's create the final development clone. This is a full git repo. + +``` powershell +$ pyrevit clone dev --dest="C:\pyRevit\dev" --branch=develop +``` + +- `--branch=`: Specify a specific branch to be cloned + +#### Installing Custom Clones + +You can also use the clone command to install your own pyRevit clones from any git url. This is done by providing `--source=` or `--source=` depending on if you're cloning from a git repo or an archive. + +``` powershell +$ pyrevit clone mypyrevit --source="https://www.github.com/my-pyrevit.git" --dest="C:\pyRevit\mypyrevit" --branch=develop +``` + +Or install from a ZIP archive using ``: + +``` powershell +$ pyrevit clone mypyrevit base --source="\\network\my-pyrevit.ZIP" --dest="C:\pyRevit\mypyrevit" +``` + +### Maintaining Clones + +You can see a list of registered clones using. Notice the full clones and the no-git clones are listed separately: + +``` powershell +$ pyrevit clones + +==> Registered Clones (full git repos) +Name: "master" | Path: "%APPDATA%\pyRevit\pyRevit" +Name: "dev" | Path: "C:\pyRevit\dev" + +==> Registered Clones (deployed from archive) +Name: "main" | Path: "C:\pyRevit\main" +``` + +Get info on a clone or open the path in file explorer: + +``` powershell +pyrevit clones (info | open) + +$ pyrevit clones info master # get info on master clone +$ pyrevit clones open dev # open dev in file explorer +``` + +Get info on available engines and deployments in a clone: + +``` powershell +pyrevit clones deployments +pyrevit clones engines + +$ pyrevit clones deployments dev # print a list of deployments +$ pyrevit clones engines dev # print a list of engines +``` + +Add existing clones (created without using `pyrevit`), remove, rename, and delete clones: + +``` powershell +pyrevit clones add [--log=] +$ pyrevit clones add newclone "C:Some\Path" # register existing clone + +pyrevit clones forget (--all | ) [--log=] +$ pyrevit clones forget newclone # forget a clone (does not delete) +$ pyrevit clones forget --all # for get all clones + +pyrevit clones rename [--log=] +$ pyrevit clones rename main base # rename clone `main` to `base` + +pyrevit clones delete [(--all | )] [--clearconfigs] [--log=] +$ pyrevit clones delete base # delete clone `base` +$ pyrevit clones delete --all # delete all clones +$ pyrevit clones delete --all --clearconfigs # delete all clones and clear configurations +``` + +#### Managing Git Clones + +Get info about branch, version and current head commit: + +``` powershell +$ pyrevit clones branch dev # get current branch of `dev` clone +$ pyrevit clones version dev # get current version of `dev` clone +$ pyrevit clones commit dev # get current head commit of `dev` clone +$ pyrevit clones origin dev # get origin url for `dev` clone +``` + +Setting current branch: + +``` powershell +pyrevit clones branch [] [--log=] + +$ pyrevit clones branch dev master # changing current branch to master for `dev` clone +``` + +Setting current version: + +``` powershell +pyrevit clones version [] [--log=] + +$ pyrevit clones version dev v4.6 # changing current version to v4.6 for `dev` clone +``` + +Setting current head commit: + +``` powershell +pyrevit clones commit [] [--log=] + +$ pyrevit clones commit dev b06ec244ce81f521115926924e7322b22b161b54 # changing current commit for `dev` clone +``` + +Setting origin remote url: + +``` powershell +pyrevit clones origin --reset [--log=] +pyrevit clones origin [] [--log=] + +$ # changing origin remote url for `dev` clone +$ pyrevit clones origin dev https://www.git.com/repo.git + +$ # resetting origin remote url back to default for `dev` clone +$ pyrevit clones origin dev --reset +``` + +#### Updating Clones + +The update command automatically updates full git clones using git pull and no-git clones by downloading and replacing all contents: + +``` powershell +pyrevit clones update (--all | ) [--log=] + +$ pyrevit clones update --all # update all clones +$ pyrevit clones update dev # update `dev` clone only +``` + +### Attaching pyRevit to Installed Revits + +`pyrevit` can detect the exact installed Revit versions on your machine. You can use the commands below to attach any pyRevit clone to any or all installed Revits. Make sure to specify the clone to be attached and the desired engine version: + +``` powershell +pyrevit attach (latest | dynamosafe | ) ( | --installed | --attached) [--allusers] [--log=] + +$ pyrevit attach dev latest --installed # attach `dev` clone to all installed Revits using latest engine +$ pyrevit attach dev 277 --installed # attach `dev` clone to all installed Revits using 277 engine +$ pyrevit attach dev dynamosafe 2018 # attach `dev` clone to Revit 2018 using an engine that has no conflicts with Dynamo BIM +``` + +- `--alusers`: Use this switch to attach for all users. It creates the manifest files inside the `%PROGRAMDATA%/Autodesk/Revit/Addons` instead of `%APPDATA%/Autodesk/Revit/Addons` +- `--attached`: This options is helpful when updating existing attachments e.g. specifying a new engine but keeping the same attachments as before + +List all the attachments: + +``` powershell +$ pyrevit attached + +==> Attachments +Autodesk Revit 2013 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2014 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2015 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2016 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2017 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2018.3.1 | Clone: "dev" | Engine: "277" +Autodesk Revit 2019 First Customer Ship | Clone: "dev" | Engine: "277" +``` + +You can also switch between the attached clones using the switch command: + +``` powershell +pyrevit switch + +$ pyrevit attached + +==> Attachments +Autodesk Revit 2018.3.1 | Clone: "base" | Engine: "279" +Autodesk Revit 2019 First Customer Ship | Clone: "dev" | Engine: "273" + +$ pyrevit switch main + +==> Attachments +Autodesk Revit 2018.3.1 | Clone: "main" | Engine: "279" +Autodesk Revit 2019 First Customer Ship | Clone: "main" | Engine: "273" +``` + +The switch command with detect all the existing attachments and will remap them to the new clone using the same engine. + +Detaching pyRevit from a specific Revit version or all installed: + +``` powershell +pyrevit detach (--all | ) [--log=] + +$ pyrevit detach --all +$ pyrevit detach 2019 +``` + +## Managing pyRevit extensions + +### Finding Extensions + +``` powershell +pyrevit extensions search +pyrevit extensions (info | help | open) + +$ pyrevit extensions search apex # search for an extension with apex in name +$ pyrevit extensions info apex # get info on extension with apex in name +``` + +- ``: Regular Expression (REGEX) pattern to search for + +### Installing Extensions + +``` powershell +pyrevit extend [--branch=] [--log=] + +$ pyrevit extend pyApex "C:\pyRevit\Extensions" # install pyApex extension +``` + +- ``: The destination directory will be added to pyRevit extensions search paths automatically and will be loaded on the next pyRevit reload. +- `--branch`: Specific branch of the extension repo to be installed + +To installing your own extensions, you'll need to specify what type if extension you're installing (ui or lib) and provide the url: + +``` powershell +pyrevit extend (ui | lib) [--branch=] [--log=] + +$ pyrevit extend ui MyExtension "https://www.github.com/my-extension.git" "C:\pyRevit\Extensions" +``` + +List all installed extensions: + +``` powershell +pyrevit extensions +``` + +### Managing Installed Extensions + +Delete an extension completely using: + +``` powershell +pyrevit extensions delete [--log=] + +$ pyrevit extensions delete pyApex +``` + +Set origin url on an extension using: + +``` powershell +pyrevit extensions origin --reset [--log=] +pyrevit extensions origin [] [--log=] + +$ # changing origin remote url for `pyApex` extension +$ pyrevit extensions origin pyApex https://www.git.com/repo.git + +$ # resetting origin remote url back to default for `pyApex` extension +$ pyrevit extensions origin pyApex --reset +``` + +Add, remove extension search paths for all your existing extensions: + +``` powershell +pyrevit extensions paths +pyrevit extensions paths forget --all [--log=] +pyrevit extensions paths (add | forget) [--log=] + +$ pyrevit extensions paths add "C:\pyRevit\MyExtensions" # add a search path +$ pyrevit extensions paths forget --all # forget all search paths +``` + +Enable or Disable an extension in pyRevit config file: + +``` powershell +pyrevit extensions (enable | disable) [--log=] + +$ pyrevit extensions enable pyApex +``` + +Getting info, opening help or installed directory: + +``` powershell +pyrevit extensions (info | help | open) + +$ pyrevit extensions info apex # get info on extension with apex in name +$ pyrevit extensions help apex # open help page +$ pyrevit extensions open apex # open path in file explorer +``` + +#### Updating Extensions + +``` powershell +pyrevit extensions update (--all | ) [--log=] + +$ pyrevit extensions update --all # update all installed extension +$ pyrevit extensions update pyApex # update pyApex extension +``` + +### Managing Extensions Lookup Sources + +`pyrevit` can lookup in other places when searching for extensions. This means that you can define a `json` file with all your private extensions and add the path to the `pyrevit` sources. Your extensions will show up in search results from then on and can be installed by their name: + +``` powershell +pyrevit extensions sources +pyrevit extensions sources forget --all [--log=] +pyrevit extensions sources (add | forget) [--log=] + +$ pyrevit extensions sources add "https://www.github.com/me/extensions.json" +``` + +- ``: Can be a `json` file path or web url + +## Getting Environment Info + +Use `env` command to get info about the current `pyrevit` environment: + +``` +$ pyrevit env + +==> Registered Clones (full git repos) +Name: "dev" | Path: "...\pyRevit" + +==> Registered Clones (deployed from archive) +Name: "master" | Deploy: "basepublic" | Path: "C:\Program Files\pyRevit-Master" +Name: "core" | Deploy: "core" | Path: "C:\tmp\core" +Name: "base" | Deploy: "base" | Path: "C:\tmp\base" + +==> Attachments +Autodesk Revit 2013 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2014 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2015 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2016 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2017 First Customer Ship | Clone: "dev" | Engine: "277" +Autodesk Revit 2018.3.1 | Clone: "dev" | Engine: "277" +Autodesk Revit 2019 First Customer Ship | Clone: "dev" | Engine: "277" + +==> Installed UI Extensions +Name: "pyApex" | Repo: "" | Installed: "C:\tmp\exts\pyApex.extension" + +==> Installed Library Extensions + +==> Extension Search Paths +C:\Program Files +C:\tmp\exts +... + +==> Extension Sources - Default +https://github.com/eirannejad/pyRevit/raw/master/extensions/extensions.json + +==> Extension Sources - Additional + +==> Installed Revits +Autodesk Revit 2013 First Customer Ship | Version: 12.2.21203 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2013\" +Autodesk Revit 2014 First Customer Ship | Version: 13.3.8151 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit Architecture 2014\" +Autodesk Revit 2015 First Customer Ship | Version: 15.0.136.0 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2015\" +Autodesk Revit 2016 First Customer Ship | Version: 16.0.428.0 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2016\" +Autodesk Revit 2017 First Customer Ship | Version: 17.0.416.0 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2017\" +Autodesk Revit 2018.3.1 | Version: 18.3.1.2 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2018\" +Autodesk Revit 2019 First Customer Ship | Version: 19.0.0.405 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2019\" + +==> Running Revit Instances + +==> User Environment +Microsoft Windows 10 [Version 10.0.17134] +Executing User: LEO-W10\LeoW10 +Active User: LEO-W10\LeoW10 +Adming Access: Yes +%APPDATA%: "C:\Users\LeoW10\AppData\Roaming" +Latest Installed .Net Framework: "4.7.2" +pyRevit CLI 0.2.18.0 +``` + +## Configuring pyRevit + +Use `config` command to configure pyRevit on your machine from an existing template configuration file: + +``` powershell +pyrevit config [--log=] + +$ pyrevit config "C:\myPyRevitTemplateConfig.ini" +``` + +Configuring core configurations: + +``` powershell +pyrevit configs logs [(none | verbose | debug)] [--log=] +$ pyrevit configs logs verbose # set logging to verbose +``` + +``` powershell +pyrevit configs allowremotedll [(enable | disable)] [--log=] +$ pyrevit configs allowremotedll enable # allow remote dll loads in dotnet +``` +``` powershell +pyrevit configs checkupdates [(enable | disable)] [--log=] +$ pyrevit configs checkupdates enable # enable check updates on startup +``` +``` powershell +pyrevit configs autoupdate [(enable | disable)] [--log=] +$ pyrevit configs autoupdate enable # enable auto-update on startup +``` +``` powershell +pyrevit configs rocketmode [(enable | disable)] [--log=] +$ pyrevit configs rocketmode enable # enable rocket mode +``` +``` powershell +pyrevit configs filelogging [(enable | disable)] [--log=] +$ pyrevit configs filelogging enable # enable file logging +``` +``` powershell +pyrevit configs loadbeta [(enable | disable)] [--log=] +$ pyrevit configs loadbeta enable # enable loading beta tools +``` +``` powershell +pyrevit configs usagelogging +pyrevit configs usagelogging enable (file | server) [--log=] +pyrevit configs usagelogging disable [--log=] +$ pyrevit configs usagelogging enable file "C:\logs" # enable usage logging to file +$ pyrevit configs usagelogging enable server "http://server" # enable usage logging to server +``` +``` powershell +pyrevit configs outputcss [] [--log=] +$ pyrevit configs outputcss "C:\myOutputStyle.css" # setting custom output window styling +``` + +### Configuring Sensitive Tools + +You can Enable/Disable a few tools in pyRevit configurations: + +``` powershell +pyrevit configs usercanupdate [(Yes | No)] [--log=] +pyrevit configs usercanextend [(Yes | No)] [--log=] +pyrevit configs usercanconfig [(Yes | No)] [--log=] +``` + +- `usercanupdate`: Enable/Disable Update tool in pyRevit main tools +- `usercanextend`: Enable/Disable Extensions tool in pyRevit main tools +- `usercanconfig`: Enable/Disable Settings tool in pyRevit main tools + +### Configuring Your Own Options + +Use the `configs` command to configure your custom config options. Specify the option in `section:option` format: + +``` powershell +pyrevit configs (enable | disable) [--log=] +pyrevit configs [] [--log=] + +$ pyrevit configs mysection:myswitch enable # set myswitch to True +$ pyrevit configs mysection:myvalue 12 # set myvalue to 12 +``` + +### Using Config as Seed + +Seed the configuration to `%PROGRAMDATA%/pyRevit` to be used as basis for pyRevit configurtions when configuring pyRevit using System account on a user machine: + +``` powershell +pyrevit configs seed [--lock] [--log=] +$ pyrevit configs seed +``` +- `--lock`: Locks the file for current user only. pyRevit will now allow changing the configurations if the seed config file is not writable. + +## Extra Revit-Related Functionality + +List installed or running Revits: + +``` powershell +pyrevit revits [--installed] [--log=] + +$ pyrevit revits --installed # list installed revits +Autodesk Revit 2016 Service Pack 2 | Version: 16.0.490.0 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2016\" +Autodesk Revit 2018.3 | Version: 18.3.0.81 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2018\" +Autodesk Revit 2019.1 | Version: 19.1.0.112 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2019\" +``` + +Close all or specific running Revit: +``` powershell +pyrevit revits killall [] [--log=] + +$ pyrevit revits killall 2018 # close all Revit 2018s +``` + +Generate a report from `RVT` or `RFA` file info: + +``` powershell +pyrevit revits fileinfo [--csv=] + +$ pyrevit revits fileinfo "C:\model.rvt" +Created in: Autodesk Revit 2018.3.1 (20180423_1000(x64)) +Workshared: Yes +Central Model Path: \\central\path\Project3.rvt +Last Saved Path: C:\Downloads\Project3.rvt +Document Id: bfb3db00-ca65-4c6a-aa64-329761e5d0ca +Open Workset Settings: LastViewed +Document Increment: 2 +``` + +- `file_or_dir_path`: could be a file or directory +- `--csv=`: Write output to specified CSV file + + +## CLI Execution of Python Scripts + +You can run a python script in any of the installed Revit versions from the command prompt. + +``` powershell +pyrevit run [--revit=] + +$ pyrevit run "C:\myscript.py" + +# the execution environment information will be printed after completion +==> Execution Environment +Execution Id: "51066afe-022c-4dba-bdc5-d35d451b998f" +Product: Autodesk Revit 2019 First Customer Ship | Version: 19.0.0.405 | Language: 1033 | Path: "C:\Program Files\Autodesk\Revit 2019\" +Clone: Name: "dev" | Path: "...\pyRevit" +Engine: KernelName:"IronPython" | Version: "277" | Path: "...\pyRevit\bin\engines\277" | Desc: "Compatible with RevitPythonShell" | Compatible: "" +Script: "C:\myscript.py" +Working Directory: "...\Temp\51066afe-022c-4dba-bdc5-d35d451b998f" +Journal File: "...\Temp\51066afe-022c-4dba-bdc5-d35d451b998f\PyRevitRunner_51066afe-022c-4dba-bdc5-d35d451b998f.txt" +Manifest File: "...\Temp\51066afe-022c-4dba-bdc5-d35d451b998f\PyRevitRunner.addin" +Log File: "...\Temp\51066afe-022c-4dba-bdc5-d35d451b998f\PyRevitRunner_51066afe-022c-4dba-bdc5-d35d451b998f.log" +``` + +- `--purge`: Runner creates a temporary directory under `%TEMP%` for this execution. Specifying this option, will clear the temporary files after completion. This is helpful when running a script over many files. + +If `--revit` option is not specified, the run command will use the newest installed version of Revit to run the command. You can however specify the revsion of Revit e.g. `--revit=2018` + +At either case, pyRevit uses the clone and engine verison that is attached to Revit, to run the script; So make sure that pyRevit is already attached to the Revit version you are intending to use. + +All the pyrevit modules are accessible inside the script. + +### Running a Script on Revit Models + +You can also specify a single or multiple models to `pyrevit run` command. If a model is specified, the run command selects a version of Revit that is used to create that model by default. As the previous example, you can still manually specify which version of Revit to use. be aware that Revit will fail if the model version is newer, or needs to upgrade the model if it is an older version. + + +``` powershell +pyrevit run [--revit=] [--purge] + +$ pyrevit run "C:\myscript.py" "C:\Model1.rvt" +$ pyrevit run "C:\myscript.py" "C:\All-My-Models.txt" + +# target models will be listed at the end of env report +==> Target Models +C:\Users\LeoW10\Desktop\batchtest\Project_V19.rvt +C:\Users\LeoW10\Desktop\batchtest\Project_V18.rvt + +``` + +- ``: Path to a Revit model file OR a text file listing multiple models (one model per line). + +**Important Note:** + +**The run command does not attempt to open the model in Revit.** Opening a model is a complex process and has many configurations e.g. Opening central or detached, Workset configurations, etc. The run command uses a simple journal file to bootstrap Revit and it avoid using complex journal structures to provide configurations on opening models since journals are heavily GUI based and could easily break in the future. Revit API provides ample functionality on opening models. It's best practice to open the model inside your script. + +The execution environment provides the global variable `__models__` set to a list of model file paths. Your script can read the models from this variable and attempt at opening each. + +``` python +from pyrevit import HOST_APP + +# __models__ is set to a list of model file paths +# __models__ = ['C:\model1.rvt'] +for model in __models__: + uidoc = HOST_APP.uiapp.OpenAndActivateDocument(model) + doc = uidoc.Document + # do something here with the document + +``` + +The developer is assumed to undestand the complexity and memory needs of the target models. Hence the run command does not provide any functionality to run multiple Revit instances on a single machine since it has very limited information about the resource needs of the target models. + +The pyrevit run is intended to provide a simple interface for execution of python scripts on Revit model(s) from command line. You can write your own python or PowerShell scripts that processes the list of your models, understands their resource needs and the available resources of the host machine, and starts one or many `pyrevit run` instances to execute the scripts on the models. + + +## Clear pyRevit Cache Files + +Cache files are stored under `%APPDATA%/pyRevit/`. You can clear all or specific Revit version caches. Revit needs to be closed for this operaton since it keeps the files locked if it is open: + +``` powershell +pyrevit caches clear (--all | ) [--log=] + +$ pyrevit caches clear 2018 # clear all caches for Revit 2018 +``` + +## Logging CLI Debug Messages + +With many commands you can log the complete log messages to a log file: + +``` powershell +$ pyrevit clone master --log="C:\logfile.txt" +``` + + +## Creating Shortcuts + +You can use the cli tool to create shortcuts in start menu for various tasks. + +``` powershell +pyrevit cli addshortcut [--desc=] [--allusers] + +$ pyrevit cli addshortcut "Update pyRevit" "clones update --all" +``` + +- `--desc`: Description for the Start menu shortcut +- `--alusers`: Create shortcut for all users or current user diff --git a/dev/pyRevitLabs/Tests/dfdb_tests.py b/dev/pyRevitLabs/Tests/dfdb_tests.py new file mode 100644 index 000000000..4a4b010e2 --- /dev/null +++ b/dev/pyRevitLabs/Tests/dfdb_tests.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +#pylint: disable=C0111,E0401,C0301,C0103,C0413,W0703,W0611,W0702 +"""ipy dfdb_tests.py + +Example: + ipy ./db_test.py dstorefile.txt eirannejad 20 + ipy ./db_test.py dstorefile.txt gamma 40 +""" +from __future__ import print_function +import time +import sys +import os.path as op +import random + +userdesktop = op.expandvars('%userprofile%\\desktop') +sys.path.append( + op.join(userdesktop, + r'gits\pyRevitLabs\pyRevitLabs\pyRevitLabs.DeffrelDB\bin\Debug') + ) + +# import DFFDB module +import clr +from System.Collections.Generic import List +clr.AddReference('pyRevitLabs.DeffrelDB') +import pyRevitLabs.DeffrelDB as dfdb + + +# prepare connection ========================================================== +fstore = sys.argv[1] +requester = sys.argv[2] +MAX_RECORDS = int(sys.argv[3]) + +if not op.exists(fstore): + fstore = op.join(userdesktop, fstore) + +conn = dfdb.DataBase.Connect(fstore, requester, debug='--debug' in sys.argv) + + +ERRORS = [] +ADDED_RECORDS = [] + +# prepare dbs ================================================================= +TEST_DBS = {'db1': {'desc': "Some Description"}, + 'db2': {'desc': "Some \"Complex\" Description"}, + 'db3': {'desc': "Some $^$^#&^#& Description"}} + +for dbkey in TEST_DBS: + print('='*25) + try: + conn.ReadDB(dbkey) + print('[ OK ] %s read %s' % (requester, dbkey)) + except Exception as ex: + print(ex) + if "does not exist" not in str(ex): + ERRORS.append(str(ex)) + dbdef = dfdb.DatabaseDefinition() + dbdef.Description = TEST_DBS[dbkey]['desc'] + try: + conn.CreateDB(dbkey, dbdef) + print('[ OK ] %s created %s' % (requester, dbkey)) + except Exception as ex: + print(ex) + ERRORS.append(str(ex)) + +# prepare dbs ================================================================= +TEST_TABLES = {'table1': {'desc': "Some Description"}, + 'table2': {'desc': "Some \"Complex\" Description"}, + 'table3': {'desc': "Some $^$^#&^#& Description"}} + +for dbkey in TEST_DBS: + print('='*25) + for tablekey in TEST_TABLES: + try: + tdef = conn.ReadTable(dbkey, dbkey + tablekey) + print(tdef.Name) + print(tdef.Description) + print(tdef.Fields) + print(tdef.Wires) + print('[ OK ] %s read %s:%s' % (requester, dbkey, tablekey)) + except Exception as ex: + print(ex) + if "does not exist" not in str(ex): + ERRORS.append(str(ex)) + record_id = dfdb.TextField('record_id') + record_text = dfdb.TextField('record_text') + table_def = dfdb.TableDefinition() + table_def.Fields = [record_id, record_text] + table_def.Wires = [ + dfdb.Wire('record_id', 'record_id'), + dfdb.Wire('record_id', 'cat_key') + ] + table_def.Description = TEST_TABLES[tablekey]['desc'] + try: + conn.CreateTable(dbkey, dbkey + tablekey, table_def) + print('[ OK ] %s created %s:%s' % (requester, dbkey, tablekey)) + except Exception as ex: + print(ex) + ERRORS.append(str(ex)) + + +# play with records =========================================================== +STRINGS = ['Example Keynote Text', + 'Example "Keynote" Text'] + +successful_records = 0 +while successful_records < MAX_RECORDS: + print('='*25) + target_db = random.choice(TEST_DBS.keys()) + target_table = random.choice(TEST_TABLES.keys()) + rec_idx = random.randint(1, MAX_RECORDS * 4) + table_id = target_db + target_table + ridx = '%s_%s' % (table_id, rec_idx) + rtext = random.choice(STRINGS) + try: + print('[ OK ] %s adding %s' % (requester, ridx)) + conn.InsertRecord(target_db, table_id, ridx, {'record_text': rtext}) + successful_records += 1 + ADDED_RECORDS.append((target_db, table_id, ridx, rtext)) + except Exception as ex: + print(ex) + if "already exists" not in str(ex): + ERRORS.append(str(ex)) + +# # batch insert +# successful_records = 0 +# target_db = random.choice(TEST_DBS.keys()) +# target_table = random.choice(TEST_TABLES.keys()) +# table_id = target_db + target_table +# print('[ OK ] %s is batch inserting into %s' % (requester, table_id)) +# try: +# conn.BEGIN(target_db, table_id) + +# while successful_records < MAX_RECORDS: +# print('='*25) +# rec_idx = random.randint(1, MAX_RECORDS * 4) +# ridx = '%s_%s' % (table_id, rec_idx) +# rtext = random.choice(STRINGS) +# try: +# print('[ OK ] %s adding %s' % (requester, ridx)) +# conn.InsertRecord(target_db, table_id, ridx, {'record_text': rtext}) +# successful_records += 1 +# ADDED_RECORDS.append((target_db, table_id, ridx, rtext)) +# except Exception as ex: +# print(ex) +# if "already exists" not in str(ex): +# ERRORS.append(str(ex)) +# conn.END() + +# except Exception as ex: +# print(ex) +# if "is restricted by" not in str(ex): +# ERRORS.append(str(ex)) + +# # verify inserted records ==================================================== +# PASSED_INSERT_TEST = True +# for record_log in ADDED_RECORDS: +# print('Verifying: {}'.format(record_log)) +# try: +# rfields = conn.ReadRecord(record_log[0], record_log[1], record_log[2]) +# if not rfields: +# ERRORS.append('Failed reading record: {}'.format(record_log[2])) +# PASSED_INSERT_TEST = False +# if not rfields['record_text'] == record_log[3]: +# ERRORS.append( +# 'Failed record text matching: {} \"{}\" != \"{}\"' +# .format(record_log[2], rfields['record_text'], record_log[3]) +# ) +# PASSED_INSERT_TEST = False +# except Exception as ex: +# print(ex) +# ERRORS.append(str(ex)) +# PASSED_INSERT_TEST = False + +# if ERRORS: +# print('[ FAIL ] %s has failures:' % (requester)) +# for error in ERRORS: +# print(error) + +# if PASSED_INSERT_TEST: +# print('[ OK ] %s passed all read tests for %s records' +# % (requester, len(ADDED_RECORDS))) +# else: +# print('[ FAIL ] %s failed some read tests.' % (requester)) diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/CommonUtils.cs b/dev/pyRevitLabs/pyRevitLabs.Common/CommonUtils.cs new file mode 100644 index 000000000..a7a60fb28 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/CommonUtils.cs @@ -0,0 +1,237 @@ +using OpenMcdf; +using System; +using System.Text; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Runtime.InteropServices; +using System.Security.AccessControl; +using System.Text.RegularExpressions; +using IWshRuntimeLibrary; + +using NLog; + +namespace pyRevitLabs.Common { + public static class CommonUtils + { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + [DllImport("ole32.dll")] private static extern int StgIsStorageFile([MarshalAs(UnmanagedType.LPWStr)] string pwcsName); + + public static bool VerifyFile(string filePath) { + return System.IO.File.Exists(filePath); + } + + public static bool VerifyPath(string path) { + return Directory.Exists(path); + } + + // helper for deleting directories recursively + // @handled @logs + public static void DeleteDirectory(string targetDir, bool verbose = true) + { + if (CommonUtils.VerifyPath(targetDir)) { + if (verbose) + logger.Debug("Recursive deleting directory \"{0}\"", targetDir); + string[] files = Directory.GetFiles(targetDir); + string[] dirs = Directory.GetDirectories(targetDir); + + try { + foreach (string file in files) { + System.IO.File.SetAttributes(file, FileAttributes.Normal); + System.IO.File.Delete(file); + } + + foreach (string dir in dirs) { + DeleteDirectory(dir, verbose: false); + } + + Directory.Delete(targetDir, false); + } + catch (Exception ex) { + throw new pyRevitException(string.Format("Error recursive deleting directory \"{0}\" | {1}", + targetDir, ex.Message)); + } + } + } + + // helper for copying a directory recursively + // @handled @logs + public static void CopyDirectory(string sourceDir, string destDir) { + logger.Debug("Copying \"{0}\" to \"{1}\"", sourceDir, destDir); + try { + // create all of the directories + foreach (string dirPath in Directory.GetDirectories(sourceDir, "*", + SearchOption.AllDirectories)) + Directory.CreateDirectory(dirPath.Replace(sourceDir, destDir)); + + // copy all the files & Replaces any files with the same name + foreach (string newPath in Directory.GetFiles(sourceDir, "*.*", + SearchOption.AllDirectories)) + System.IO.File.Copy(newPath, newPath.Replace(sourceDir, destDir), true); + } + catch (Exception ex) { + throw new pyRevitException( + string.Format("Error copying \"{0}\" to \"{1}\" | {2}", sourceDir, destDir, ex.Message) + ); + } + } + + public static void ConfirmPath(string path) + { + Directory.CreateDirectory(path); + } + + public static void ConfirmFile(string filepath) + { + ConfirmPath(Path.GetDirectoryName(filepath)); + if (!System.IO.File.Exists(filepath)) { + var file = System.IO.File.CreateText(filepath); + file.Close(); + } + } + + public static string DownloadFile(string url, string destPath) + { + if (CheckInternetConnection()) { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + using (var client = new WebClient()) { + client.DownloadFile(url, destPath); + } + } + else + throw new pyRevitNoInternetConnectionException(); + + return destPath; + } + + public static bool CheckInternetConnection() + { + try { + using (var client = new WebClient()) + using (client.OpenRead("http://clients3.google.com/generate_204")) { + return true; + } + } + catch { + return false; + } + } + + public static byte[] GetStructuredStorageStream(string filePath, string streamName) + { + logger.Debug(string.Format("Attempting to read \"{0}\" stream from structured storage file at \"{1}\"", + streamName, filePath)); + int res = StgIsStorageFile(filePath); + + if (res == 0) { + CompoundFile cf = new CompoundFile(filePath); + CFStream foundStream = cf.RootStorage.TryGetStream(streamName); + if (foundStream != null) { + byte[] streamData = foundStream.GetData(); + cf.Close(); + return streamData; + } + return null; + } + else { + throw new NotSupportedException("File is not a structured storage file"); + } + } + + public static void OpenUrl(string url, string errMsg = null) { + if (CheckInternetConnection()) + Process.Start(url); + else { + if (errMsg == null) + errMsg = string.Format("Error opening url \"{0}\"", url); + + logger.Error(string.Format("{0}. No internet connection detected.", errMsg)); + } + } + + public static void SetFileSecurity(string filePath, string userNameWithDoman) { + //get file info + FileInfo fi = new FileInfo(filePath); + + //get security access + FileSecurity fs = fi.GetAccessControl(); + + //remove any inherited access + fs.SetAccessRuleProtection(true, false); + + //get any special user access + AuthorizationRuleCollection rules = + fs.GetAccessRules(true, true, typeof(System.Security.Principal.NTAccount)); + + //remove any special access + foreach (FileSystemAccessRule rule in rules) + fs.RemoveAccessRule(rule); + + //add current user with full control. + fs.AddAccessRule( + new FileSystemAccessRule(userNameWithDoman, FileSystemRights.FullControl, AccessControlType.Allow) + ); + + //add all other users delete only permissions. + //fs.AddAccessRule( + // new FileSystemAccessRule("Authenticated Users", FileSystemRights.Delete, AccessControlType.Allow) + // ); + + //flush security access. + System.IO.File.SetAccessControl(filePath, fs); + } + + public static void OpenInExplorer(string resourcePath) { + Process.Start("explorer.exe", resourcePath); + } + + public static void AddShortcut(string shortCutName, + string appName, + string pathToExe, + string args, + string workingDir, + string iconPath, + string description, + bool allUsers = false) { + string commonStartMenuPath = Environment.GetFolderPath( + allUsers ? Environment.SpecialFolder.CommonStartMenu : Environment.SpecialFolder.StartMenu + ); + string appStartMenuPath = Path.Combine(commonStartMenuPath, "Programs", appName); + + ConfirmPath(appStartMenuPath); + + string shortcutLocation = Path.Combine(appStartMenuPath, shortCutName + ".lnk"); + WshShell shell = new WshShell(); + IWshShortcut shortcut = (IWshShortcut)shell.CreateShortcut(shortcutLocation); + + shortcut.Description = "Test App Description"; + //shortcut.IconLocation = @"C:\Program Files (x86)\TestApp\TestApp.ico"; //uncomment to set the icon of the shortcut + shortcut.TargetPath = pathToExe; + shortcut.Arguments = args; + shortcut.Description = description; + shortcut.IconLocation = iconPath; + shortcut.WorkingDirectory = workingDir; + shortcut.Save(); + } + + public static string NewShortUUID() { + return Convert.ToBase64String(Guid.NewGuid().ToByteArray()); + } + + // https://en.wikipedia.org/wiki/ISO_8601 + // https://stackoverflow.com/a/27321188/2350244 + public static string GetISOTimeStamp(DateTime dtimeValue) => dtimeValue.ToString("yyyy-MM-ddTHH:mm:ssK"); + + public static string GetISOTimeStampNow() { + var dtime = DateTime.Now.ToUniversalTime(); + return GetISOTimeStamp(dtime); + } + + public static Encoding GetUTF8NoBOMEncoding() { + // https://coderwall.com/p/o59zug/encoding-multiply-files-to-utf8-without-bom-with-c + return new System.Text.UTF8Encoding(false); + } + } +} + diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/Errors.cs b/dev/pyRevitLabs/pyRevitLabs.Common/Errors.cs new file mode 100644 index 000000000..05499aab0 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/Errors.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace pyRevitLabs.Common { + // ERROR CODES =================================================================================================== + // error codes are to be used for non-critical, non-breaking errors + + // available error codes + public enum ErrorCodes { + NoErrors, + DefaultedToAllUsersConfigFile, + MoreThanOneItemMatched, + } + + // error handling singleton + // >>> Errors.LatestError = ErrorCodes.OccuredErrorCode; + public sealed class Errors { + private static Errors instance = null; + private static readonly object padlock = new object(); + + Errors() { + } + + public static Errors Instance { + get { + lock (padlock) { + if (instance == null) { + instance = new Errors(); + } + return instance; + } + } + } + + public static ErrorCodes LatestError { get; set; } = ErrorCodes.NoErrors; + } + + // EXCEPTIONS ==================================================================================================== + // exceptions to be used for all breaking, critical errors + + // base exception + public class pyRevitException : Exception { + public pyRevitException() { } + + public pyRevitException(string message) : base(message) { } + + public pyRevitException(string message, Exception innerException) : base(message, innerException) { } + } + + // resource exceptions + public class pyRevitResourceMissingException : pyRevitException { + public pyRevitResourceMissingException() { } + + public pyRevitResourceMissingException(string resoucePath) { Path = resoucePath; } + + public string Path { get; set; } + + public override string Message { + get { + return String.Format("\"{0}\" does not exist.", Path); + } + } + } + + public class pyRevitNoInternetConnectionException : pyRevitException { + public pyRevitNoInternetConnectionException() { } + + public override string Message { + get { + return "No internet connection detected."; + } + } + + } + +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/Extensions.cs b/dev/pyRevitLabs/pyRevitLabs.Common/Extensions.cs new file mode 100644 index 000000000..0f81e3e7b --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/Extensions.cs @@ -0,0 +1,298 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Text.RegularExpressions; +using System.Globalization; + +namespace pyRevitLabs.Common.Extensions { + public static class ConstHelper { + /// + /// Remaps international characters to ascii compatible ones + /// based of: https://meta.stackexchange.com/questions/7435/non-us-ascii-characters-dropped-from-full-profile-url/7696#7696 + /// + /// Charcter to remap + /// Remapped character + public static string RemapInternationalCharToAscii(char c) { + string s = c.ToString().ToLowerInvariant(); + if ("àåáâäãåą".Contains(s)) { + return "a"; + } + else if ("èéêëę".Contains(s)) { + return "e"; + } + else if ("ìíîïı".Contains(s)) { + return "i"; + } + else if ("òóôõöøőð".Contains(s)) { + return "o"; + } + else if ("ùúûüŭů".Contains(s)) { + return "u"; + } + else if ("çćčĉ".Contains(s)) { + return "c"; + } + else if ("żźž".Contains(s)) { + return "z"; + } + else if ("śşšŝ".Contains(s)) { + return "s"; + } + else if ("ñń".Contains(s)) { + return "n"; + } + else if ("ýÿ".Contains(s)) { + return "y"; + } + else if ("ğĝ".Contains(s)) { + return "g"; + } + else if (c == 'ř') { + return "r"; + } + else if (c == 'ł') { + return "l"; + } + else if (c == 'đ') { + return "d"; + } + else if (c == 'ß') { + return "ss"; + } + else if (c == 'þ') { + return "th"; + } + else if (c == 'ĥ') { + return "h"; + } + else if (c == 'ĵ') { + return "j"; + } + else { + return ""; + } + } + } + + public static class FileSizeExtension { + public static string CleanupSize(this float bytes) { + var sizes = new List { "Bytes", "KB", "MB", "GB", "TB" }; + + if (bytes == 0) + return "0 Byte"; + + var i = (int)Math.Floor(Math.Log(bytes) / Math.Log(1024)); + if (i >= 0 && i <= sizes.Count) + return Math.Round(bytes / Math.Pow(1024, i), 2) + " " + sizes[i]; + else + return bytes + " Bytes"; + } + + public static string CleanupSize(this long bytes) { + return CleanupSize((float)bytes); + } + + public static string CleanupSize(this int bytes) { + return CleanupSize((float)bytes); + } + } + + public static class InputExtensions { + public static int LimitToRange(this int value, int inclusiveMinimum, int inclusiveMaximum) { + if (value < inclusiveMinimum) { return inclusiveMinimum; } + if (value > inclusiveMaximum) { return inclusiveMaximum; } + return value; + } + } + + public static class StringExtensions { + private static Regex DriveLetterFinder = new Regex(@"^(?[A-Za-z]):"); + private static Regex GuidFinder = new Regex(@".*(?[0-9A-Fa-f]{8}[-]" + + "[0-9A-Fa-f]{4}[-]" + + "[0-9A-Fa-f]{4}[-]" + + "[0-9A-Fa-f]{4}[-]" + + "[0-9A-Fa-f]{12}).*"); + + public static string GetDisplayPath(this string sourceString) { + var separator = Path.AltDirectorySeparatorChar.ToString(); + return sourceString.Replace("||", separator) + .Replace("|\\", separator) + .Replace("|", separator) + .Replace(Path.DirectorySeparatorChar.ToString(), separator); + } + + public static string TripleDot(this string sourceString, uint maxLength) { + if (sourceString.Length > maxLength && maxLength > 3) + return sourceString.Substring(0, (int)maxLength - 3) + "..."; + else + return sourceString; + } + + public static string NullToNA(this string sourceString) { + if (sourceString == "" || sourceString == null) + return "N/A"; + else + return sourceString; + } + + public static string NormalizeAsPath(this string path) { + var normedPath = + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var match = DriveLetterFinder.Match(normedPath); + if (match.Success) { + var driveLetter = match.Groups["drive"].Value + ":"; + normedPath = normedPath.Replace(driveLetter, driveLetter.ToUpper()); + } + + return normedPath; + } + + public static Version ConvertToVersion(this string version) { + if (!version.Contains(".")) + version = version + ".0"; + return new Version(version); + } + + public static List ConvertFromCommaSeparatedString(this string commaSeparatedValue) { + return new List(commaSeparatedValue.Split(',')); + } + + public static List ConvertFromTomlListString(this string tomlListString) { + var cleanedValue = tomlListString.Replace("[", "").Replace("]", ""); + var quotedValues = new List(cleanedValue.Split(',')); + var results = new List(); + var valueFinder = new Regex(@"'(?.+)'"); + foreach(var value in quotedValues) { + var m = valueFinder.Match(value); + if (m.Success) + results.Add(m.Groups["value"].Value); + } + return results; + } + + public static Dictionary ConvertFromTomlDictString(this string tomlDictString) { + var cleanedValue = tomlDictString.Replace("{", "").Replace("}", ""); + var quotedKeyValuePairs = new List(cleanedValue.Split(',')); + var results = new Dictionary(); + var valueFinder = new Regex(@"'(?.+)'\s*:\s*'(?.+)'"); + foreach (var keyValueString in quotedKeyValuePairs) { + var m = valueFinder.Match(keyValueString); + if (m.Success) + results[m.Groups["key"].Value] = m.Groups["value"].Value; + } + return results; + } + + public static Guid ExtractGuid(this string inputString) { + var zeroGuid = new Guid(); + var m = GuidFinder.Match(inputString); + if (m.Success) { + try { + var guid = new Guid(m.Groups["guid"].Value); + if (guid != zeroGuid) + return guid; + } catch { + return zeroGuid; + } + } + return zeroGuid; + } + + public static bool IsValidHttpUrl(this string sourceString) { + Uri uriResult; + return Uri.TryCreate(sourceString, UriKind.Absolute, out uriResult) + && (uriResult.Scheme == Uri.UriSchemeHttp || uriResult.Scheme == Uri.UriSchemeHttps); + } + + /// + /// Creates a URL And SEO friendly slug + /// + /// Text to slugify + /// Max length of slug + /// URL and SEO friendly string + public static string UrlFriendly(this string text, int maxLength = 0) { + // Return empty value if text is null + if (text == null) return ""; + var normalizedString = text + // Make lowercase + .ToLowerInvariant() + // Normalize the text + .Normalize(NormalizationForm.FormD); + var stringBuilder = new StringBuilder(); + var stringLength = normalizedString.Length; + var prevdash = false; + var trueLength = 0; + char c; + for (int i = 0; i < stringLength; i++) { + c = normalizedString[i]; + switch (CharUnicodeInfo.GetUnicodeCategory(c)) { + // Check if the character is a letter or a digit if the character is a + // international character remap it to an ascii valid character + case UnicodeCategory.LowercaseLetter: + case UnicodeCategory.UppercaseLetter: + case UnicodeCategory.DecimalDigitNumber: + if (c < 128) + stringBuilder.Append(c); + else + stringBuilder.Append(ConstHelper.RemapInternationalCharToAscii(c)); + prevdash = false; + trueLength = stringBuilder.Length; + break; + // Check if the character is to be replaced by a hyphen but only if the last character wasn't + case UnicodeCategory.SpaceSeparator: + case UnicodeCategory.ConnectorPunctuation: + case UnicodeCategory.DashPunctuation: + case UnicodeCategory.OtherPunctuation: + case UnicodeCategory.MathSymbol: + if (!prevdash) { + stringBuilder.Append('-'); + prevdash = true; + trueLength = stringBuilder.Length; + } + break; + } + // If we are at max length, stop parsing + if (maxLength > 0 && trueLength >= maxLength) + break; + } + // Trim excess hyphens + var result = stringBuilder.ToString().Trim('-'); + // Remove any excess character to meet maxlength criteria + return maxLength <= 0 || result.Length <= maxLength ? result : result.Substring(0, maxLength); + } + } + + public static class DateTimeExtensions { + public static string NeatTime(this DateTime sourceDate) { + return String.Format("{0:dd/MM/yyyy HH:mm:ss}", sourceDate); + } + } + + public static class StringEnumerableExtensions { + public static string ConvertToCommaSeparatedString(this IEnumerable sourceValues) { + return string.Join(",", sourceValues); + } + + public static string ConvertToTomlListString(this IEnumerable sourceValues) { + var quotedValues = new List(); + foreach (var value in sourceValues) + quotedValues.Add(string.Format("'{0}'", value)); + return "[" + string.Join(",", quotedValues) + "]"; + } + } + + public static class StringDictionaryExtensions { + public static string ConvertToTomlDictString(this IDictionary sourceValues) { + var quotedValues = new List(); + foreach (var keyValuePair in sourceValues) { + string quotedKey = string.Format("'{0}'", keyValuePair.Key); + string quotedValue = string.Format("'{0}'", keyValuePair.Value); + quotedValues.Add(string.Format("{0}:{1}", quotedKey, quotedValue)); + } + return "{" + string.Join(",", quotedValues) + "}"; + } + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/GitInstaller.cs b/dev/pyRevitLabs/pyRevitLabs.Common/GitInstaller.cs new file mode 100644 index 000000000..0cc952b17 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/GitInstaller.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using LibGit2Sharp; +using NLog; + +namespace pyRevitLabs.Common { + // git exceptions + public class pyRevitInvalidGitCloneException : pyRevitException { + public pyRevitInvalidGitCloneException() { } + + public pyRevitInvalidGitCloneException(string invalidClonePath) { Path = invalidClonePath; } + + public string Path { get; set; } + + public override string Message { + get { + return String.Format("Path \"{0}\" is not a valid git clone.", Path); + } + } + } + + public enum UpdateStatus { + Error, + Conflicts, + NonFastForward, + FastForward, + UpToDate, + } + + public static class GitInstaller { + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + // git identity defaults + private const string commiterName = "eirannejad"; + private const string commiterEmail = "eirannejad@gmail.com"; + private static Identity commiterId = new Identity(commiterName, commiterEmail); + + + // public methods + // clone a repo to given destination + // @handled @logs + public static Repository Clone(string repoPath, string branchName, string destPath, bool checkout = true) { + // build options and clone + var cloneOps = new CloneOptions() { Checkout = checkout, BranchName = branchName }; + + try { + // attempt at cloning the repo + logger.Debug("Cloning \"{0}:{1}\" to \"{2}\"", repoPath, branchName, destPath); + Repository.Clone(repoPath, destPath, cloneOps); + + // make repository object and return + return new Repository(destPath); + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + } + + // checkout a repo branch. Looks up remotes for that branch if the local doesn't exist + // @handled @logs + public static void CheckoutBranch(string repoPath, string branchName) { + try { + var repo = new Repository(repoPath); + + // get local branch, or make one (and fetch from remote) if doesn't exist + Branch targetBranch = repo.Branches[branchName]; + if (targetBranch == null) { + logger.Debug(string.Format("Branch \"{0}\" does not exist in local clone. " + + "Attemping to checkout from remotes...", branchName)); + // lookup remotes for the branch otherwise + foreach (Remote remote in repo.Network.Remotes) { + string remoteBranchPath = remote.Name + "/" + branchName; + Branch remoteBranch = repo.Branches[remoteBranchPath]; + if (remoteBranch != null) { + // create a local branch, with remote branch as tracking; update; and checkout + Branch localBranch = repo.CreateBranch(branchName, remoteBranch.Tip); + repo.Branches.Update(localBranch, b => b.Remote = remote.Name); + } + } + } + + // now checkout the branch + logger.Debug("Checkign out branch \"{0}\"...", branchName); + Commands.Checkout(repo, branchName); + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + } + + // rebase current branch and pull from master + // @handled @logs + public static UpdateStatus ForcedUpdate(string repoPath) { + logger.Debug("Force updating repo \"{0}\"...", repoPath); + try { + var repo = new Repository(repoPath); + var options = new PullOptions(); + options.FetchOptions = new FetchOptions(); + + // before updating, let's first + // forced checkout to overwrite possible local changes + // Re: https://github.com/eirannejad/pyRevit/issues/229 + var checkoutOptions = new CheckoutOptions(); + checkoutOptions.CheckoutModifiers = CheckoutModifiers.Force; + Commands.Checkout(repo, repo.Head, checkoutOptions); + + // now let's pull from the tracked remote + var res = Commands.Pull(repo, + new Signature("GitInstaller", + commiterEmail, + new DateTimeOffset(DateTime.Now)), + options); + + // process the results and let user know + if (res.Status == MergeStatus.FastForward) { + logger.Debug("Fast-Forwarded repo \"{0}\"", repoPath); + return UpdateStatus.FastForward; + } + else if (res.Status == MergeStatus.NonFastForward) { + logger.Debug("Non-Fast-Forwarded repo \"{0}\"", repoPath); + return UpdateStatus.NonFastForward; + } + else if (res.Status == MergeStatus.Conflicts) { + logger.Debug("Conflicts on updating clone \"{0}\"", repoPath); + return UpdateStatus.Conflicts; + } + + logger.Debug("Repo \"{0}\" is already up to date.", repoPath); + return UpdateStatus.UpToDate; + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + } + + // rebase current branch to a specific commit by commit hash + // @handled @logs + public static void RebaseToCommit(string repoPath, string commitHash) { + try { + var repo = new Repository(repoPath); + + // trying to find commit in current branch + logger.Debug("Searching for commit \"{0}\"...", commitHash); + foreach (Commit cmt in repo.Commits) { + if (cmt.Id.ToString().StartsWith(commitHash)) { + logger.Debug("Commit found."); + RebaseToCommit(repo, cmt); + return; + } + } + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + + // if it gets here with no errors, it means commit could not be found + // I'm avoiding throwing an exception inside my own try:catch + throw new pyRevitException(String.Format("Can not find commit with hash \"{0}\"", commitHash)); + } + + // rebase current branch to a specific tag + // @handled @logs + public static void RebaseToTag(string repoPath, string tagName) { + try { + var repo = new Repository(repoPath); + + // try to find the tag commit hash and rebase to that commit + logger.Debug("Searching for tag \"{0}\" target commit...", tagName); + foreach (Tag tag in repo.Tags) { + if (tag.FriendlyName.ToLower() == tagName.ToLower()) { + // rebase using commit hash + logger.Debug("Tag target commit found."); + RebaseToCommit(repoPath, tag.Target.Id.ToString()); + return; + } + } + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + + // if it gets here with no errors, it means commit could not be found + // I'm avoiding throwing an exception inside my own try:catch + throw new pyRevitException(String.Format("Can not find commit targetted by tag \"{0}\"", tagName)); + } + + // change origin url to the provided url + // @handled @logs + public static void SetRemoteUrl(string repoPath, string remoteName, string remoteUrl) { + try { + var repo = new Repository(repoPath); + + logger.Debug("Setting origin to \"{0}\"...", remoteUrl); + repo.Network.Remotes.Update(remoteName, r => r.Url = remoteUrl); ; + } + catch (Exception ex) { + throw new pyRevitException(ex.Message, ex); + } + } + + // check to see if a directory is a git repo + // @handled @logs + public static bool IsValidRepo(string repoPath) { + logger.Debug("Verifying repo validity \"{0}\"", repoPath); + return Repository.IsValid(repoPath); + } + + // get the checkedout branch from repopath + // @handled @logs + public static string GetCheckedoutBranch(string repoPath) { + if (IsValidRepo(repoPath)) + return new Repository(repoPath).Head.FriendlyName; + logger.Debug("Can not determine head branch for \"{0}\"", repoPath); + return null; + } + + // get the checkedout branch from repopath + // @handled @logs + public static string GetHeadCommit(string repoPath) { + if (IsValidRepo(repoPath)) + return new Repository(repoPath).Head.Tip.Id.ToString(); + logger.Debug("Can not determine head commit hash for \"{0}\"", repoPath); + return null; + } + + // get the checkedout branch from repopath + // @handled @logs + public static string GetRemoteUrl(string repoPath, string remoteName) { + if (IsValidRepo(repoPath)) { + try { + return new Repository(repoPath).Network.Remotes[remoteName].Url; + } + catch (Exception ex) { + logger.Debug("Can not determine remote \"{0}\" url for \"{1}\"", remoteName, repoPath); + throw new pyRevitException(ex.Message, ex); + } + } + return null; + } + + // private methods + // rebase current branch to a specific commit + // @handled @logs + private static void RebaseToCommit(Repository repo, Commit commit) { + logger.Debug("Rebasing to commit \"{0}\"", commit.Id); + var tempBranch = repo.CreateBranch("rebasetemp", commit); + repo.Rebase.Start(repo.Head, repo.Head, tempBranch, commiterId, new RebaseOptions()); + repo.Branches.Remove(tempBranch); + } + + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/GlobalConfigs.cs b/dev/pyRevitLabs/pyRevitLabs.Common/GlobalConfigs.cs new file mode 100644 index 000000000..9345f4766 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/GlobalConfigs.cs @@ -0,0 +1,32 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace pyRevitLabs.Common { + + // class for configuring global behaviour of libraries + public sealed class GlobalConfigs { + private static GlobalConfigs instance = null; + private static readonly object padlock = new object(); + + GlobalConfigs() { + } + + public static GlobalConfigs Instance { + get { + lock (padlock) { + if (instance == null) { + instance = new GlobalConfigs(); + } + return instance; + } + } + } + + public static bool UnderTest { get; set; } + public static bool AllClonesAreValid { get; set; } + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/Properties/AssemblyInfo.cs b/dev/pyRevitLabs/pyRevitLabs.Common/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6f87c113f --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("pyRevitLabs.Common")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("pyRevitLabs.Common")] +[assembly: AssemblyCopyright("Copyright © eirannejad 2018-2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("c5a03112-6ee1-415f-b53d-5d732d9f47ad")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1.11.0")] +[assembly: AssemblyFileVersion("0.1.11.0")] diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/Security.cs b/dev/pyRevitLabs/pyRevitLabs.Common/Security.cs new file mode 100644 index 000000000..87af82497 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/Security.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; + +namespace pyRevitLabs.Common.Security { + // access qualifiers + public static class UserAuth { + public static bool UserIsInSecurityGroup(string targetSid) { + var wi = WindowsIdentity.GetCurrent(); + foreach (var sid in wi.Groups) + if (sid.Value == targetSid) + return true; + return false; + } + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/UserEnv.cs b/dev/pyRevitLabs/pyRevitLabs.Common/UserEnv.cs new file mode 100644 index 000000000..f3f4fcfa9 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/UserEnv.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Management; +using System.Security.Principal; + +using DotNetVersionFinder; + +namespace pyRevitLabs.Common { + public static class UserEnv { + public static string GetWindowsVersion() { + // https://docs.microsoft.com/en-us/windows/desktop/SysInfo/operating-system-version + // https://stackoverflow.com/a/37700770/2350244 + return OSVersionInfo.GetOSVersionInfo().FullName; + } + + public static Version GetInstalledDotNetVersion() { + return DotNetVersion.Find(); + } + + public static string GetLoggedInUserName() { + ConnectionOptions oConn = new ConnectionOptions(); + ManagementScope oMs = new ManagementScope("\\\\localhost", oConn); + + ObjectQuery oQuery = new ObjectQuery("select * from Win32_ComputerSystem"); + ManagementObjectSearcher oSearcher = new ManagementObjectSearcher(oMs, oQuery); + ManagementObjectCollection oReturnCollection = oSearcher.Get(); + + foreach (ManagementObject oReturn in oReturnCollection) { + return oReturn["UserName"].ToString(); + } + + return null; + } + + public static bool IsRunAsAdmin() { + WindowsIdentity id = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new WindowsPrincipal(id); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + + } + + + // https://code.msdn.microsoft.com/windowsapps/How-to-determine-the-263b1850 + public class OSVersionInfo { + + public string FullName { + get { + return "Microsoft " + Name + " " + "[Version " + Major + "." + Minor + "." + Build + "]"; + } + } + + public string Name { get; set; } + public int Minor { get; set; } + public int Major { get; set; } + public int Build { get; set; } + + private OSVersionInfo() { } + + // Init OSVersionInfo object by current windows environment + public static OSVersionInfo GetOSVersionInfo() { + OperatingSystem osVersionObj = Environment.OSVersion; + + OSVersionInfo osVersionInfo = new OSVersionInfo() { + Name = GetOSName(osVersionObj), + Major = osVersionObj.Version.Major, + Minor = osVersionObj.Version.Minor, + Build = osVersionObj.Version.Build + }; + + return osVersionInfo; + } + + // Get current windows name + static string GetOSName(OperatingSystem osInfo) { + string osName = "unknown"; + + switch (osInfo.Platform) { + //for old windows kernel + case PlatformID.Win32Windows: + osName = ForWin32Windows(osInfo); + break; + //fow NT kernel + case PlatformID.Win32NT: + osName = ForWin32NT(osInfo); + break; + } + + return osName; + } + + // for old windows kernel + // this function is the child function for method GetOSName + static string ForWin32Windows(OperatingSystem osInfo) { + string osVersion = "Unknown"; + + //Code to determine specific version of Windows 95, + //Windows 98, Windows 98 Second Edition, or Windows Me. + switch (osInfo.Version.Minor) { + case 0: + osVersion = "Windows 95"; + break; + case 10: + switch (osInfo.Version.Revision.ToString()) { + case "2222A": + osVersion = "Windows 98 Second Edition"; + break; + default: + osVersion = "Windows 98"; + break; + } + break; + case 90: + osVersion = "Windows Me"; + break; + } + + return osVersion; + } + + // fow NT kernel + // this function is the child function for method GetOSName + static string ForWin32NT(OperatingSystem osInfo) { + string osVersion = "Unknown"; + + //Code to determine specific version of Windows NT 3.51, + //Windows NT 4.0, Windows 2000, or Windows XP. + switch (osInfo.Version.Major) { + case 3: + osVersion = "Windows NT 3.51"; + break; + case 4: + osVersion = "Windows NT 4.0"; + break; + case 5: + switch (osInfo.Version.Minor) { + case 0: + osVersion = "Windows 2000"; + break; + case 1: + osVersion = "Windows XP"; + break; + case 2: + osVersion = "Windows 2003"; + break; + } + break; + case 6: + switch (osInfo.Version.Minor) { + case 0: + osVersion = "Windows Vista"; + break; + case 1: + osVersion = "Windows 7"; + break; + case 2: + osVersion = "Windows 8"; + break; + case 3: + osVersion = "Windows 8.1"; + break; + } + break; + case 10: + osVersion = "Windows 10"; + break; + } + + return osVersion; + } + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/app.config b/dev/pyRevitLabs/pyRevitLabs.Common/app.config new file mode 100644 index 000000000..67cd2ed14 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj b/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj new file mode 100644 index 000000000..a4d9ff9ce --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.Common/pyRevitLabs.Common.csproj @@ -0,0 +1,106 @@ + + + + + Debug + AnyCPU + {C5A03112-6EE1-415F-B53D-5D732D9F47AD} + Library + Properties + pyRevitLabs.Common + pyRevitLabs.Common + v4.7.1 + 512 + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x64 + + + none + false + bin\Release\ + TRACE + prompt + 4 + AnyCPU + false + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.1.0 + + + 0.25.4 + + + 1.0.6 + + + 2.0.3 + + + 4.5.11 + + + 2.2.1.3 + + + + + {F935DC20-1CF0-11D0-ADB9-00C04FD58A0B} + 1 + 0 + 0 + tlbimp + False + True + + + + + + + + \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.CommonCLI/ConsoleProvider.cs b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/ConsoleProvider.cs new file mode 100644 index 000000000..b2482699a --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/ConsoleProvider.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace pyRevitLabs.CommonCLI +{ + public class ConsoleProvider + { + private enum StandardHandle : uint { + Input = unchecked((uint)-10), + Output = unchecked((uint)-11), + Error = unchecked((uint)-12) + } + + private enum FileType : uint { + Unknown = 0x0000, + Disk = 0x0001, + Char = 0x0002, + Pipe = 0x0003 + } + + private const string KEREL32_DLLNAME = "kernel32.dll"; + private const int ATTACH_PARENT_PROCESS = -1; + + [DllImport(KEREL32_DLLNAME)] + private static extern bool AllocConsole(); + + [DllImport(KEREL32_DLLNAME, SetLastError = true)] + private static extern int FreeConsole(); + + [DllImport(KEREL32_DLLNAME)] + private static extern IntPtr GetConsoleWindow(); + + [DllImport(KEREL32_DLLNAME)] + private static extern int GetConsoleOutputCP(); + + [DllImport(KEREL32_DLLNAME, SetLastError = true)] + private static extern bool AttachConsole(int dwProcessId); + + [DllImport(KEREL32_DLLNAME, SetLastError = true)] + private static extern IntPtr GetStdHandle(StandardHandle nStdHandle); + + [DllImport(KEREL32_DLLNAME, SetLastError = true)] + private static extern bool SetStdHandle(StandardHandle nStdHandle, IntPtr handle); + + [DllImport(KEREL32_DLLNAME, SetLastError = true)] + private static extern FileType GetFileType(IntPtr handle); + + public static bool HasConsole + { + get { return GetConsoleWindow() != IntPtr.Zero; } + } + + /// + /// Creates a new console instance if the process is not attached to a console already. + /// + public static void Show() + { + //#if DEBUG + if (!HasConsole) + { + AllocConsole(); + InvalidateOutAndError(); + } + //#endif + } + + /// + /// If the process has a console attached to it, it will be detached and no longer visible. Writing to the System.Console is still possible, but no output will be shown. + /// + public static void Hide() + { + //#if DEBUG + if (HasConsole) + { + SetOutAndErrorNull(); + FreeConsole(); + } + //#endif + } + + /// + /// Attach to existing console. This is helpful when running program from existing console. + /// + public static void Attach() + { + //#if DEBUG + if (!HasConsole) + { + AttachConsole(ATTACH_PARENT_PROCESS); + } + //#endif + } + + /// + /// Detach from existing console. This is helpful when running program from existing console. + /// + public static void Detach() + { + Hide(); + } + + public static void Toggle() + { + if (HasConsole) + { + Hide(); + } + else + { + Show(); + } + } + + public static void InvalidateOutAndError() + { + Type type = typeof(System.Console); + + System.Reflection.FieldInfo _out = type.GetField("_out", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + System.Reflection.FieldInfo _error = type.GetField("_error", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + System.Reflection.MethodInfo _InitializeStdOutError = type.GetMethod("InitializeStdOutError", + System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic); + + Debug.Assert(_out != null); + Debug.Assert(_error != null); + + Debug.Assert(_InitializeStdOutError != null); + + _out.SetValue(null, null); + _error.SetValue(null, null); + + _InitializeStdOutError.Invoke(null, new object[] { true }); + } + + public static void SetOutAndErrorNull() + { + Console.SetOut(TextWriter.Null); + Console.SetError(TextWriter.Null); + } + + private static bool IsRedirected(IntPtr handle) { + FileType fileType = GetFileType(handle); + + return (fileType == FileType.Disk) || (fileType == FileType.Pipe); + } + + public static void Redirect() { + if (IsRedirected(GetStdHandle(StandardHandle.Output))) { + var initialiseOut = Console.Out; + } + + bool errorRedirected = IsRedirected(GetStdHandle(StandardHandle.Error)); + if (errorRedirected) { + var initialiseError = Console.Error; + } + + AttachConsole(-1); + + if (!errorRedirected) + SetStdHandle(StandardHandle.Error, GetStdHandle(StandardHandle.Output)); + } + } +} diff --git a/dev/pyRevitLabs/pyRevitLabs.CommonCLI/Properties/AssemblyInfo.cs b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..29a589cc5 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("pyRevitLabs.CommonCLI")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("pyRevitLabs.CommonCLI")] +[assembly: AssemblyCopyright("Copyright © eirannejad 2018-2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("614720ba-c0e3-4199-9a5e-d2468ffcbfe5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("0.1.0.0")] +[assembly: AssemblyFileVersion("0.1.0.0")] diff --git a/dev/pyRevitLabs/pyRevitLabs.CommonCLI/pyRevitLabs.CommonCLI.csproj b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/pyRevitLabs.CommonCLI.csproj new file mode 100644 index 000000000..0382b68f2 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.CommonCLI/pyRevitLabs.CommonCLI.csproj @@ -0,0 +1,70 @@ + + + + + Debug + AnyCPU + {614720BA-C0E3-4199-9A5E-D2468FFCBFE5} + Library + Properties + pyRevitLabs.CommonCLI + pyRevitLabs.CommonCLI + v4.7.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + x64 + + + none + true + bin\Release\ + TRACE + prompt + 4 + AnyCPU + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + 2.0.3 + + + + + + + + \ No newline at end of file diff --git a/dev/pyRevitLabs/pyRevitLabs.CommonWPF/Controls/ActivityBar.xaml b/dev/pyRevitLabs/pyRevitLabs.CommonWPF/Controls/ActivityBar.xaml new file mode 100644 index 000000000..e43546f71 --- /dev/null +++ b/dev/pyRevitLabs/pyRevitLabs.CommonWPF/Controls/ActivityBar.xaml @@ -0,0 +1,77 @@ + + + + + + + + + + + +