Initial commit
Adds text formatter.
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Basic .gitattributes for a python repo.
|
||||
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Source files
|
||||
# ============
|
||||
*.pxd text diff=python
|
||||
*.py text diff=python
|
||||
*.py3 text diff=python
|
||||
*.pyw text diff=python
|
||||
*.pyx text diff=python
|
||||
*.pyz text diff=python
|
||||
*.pyi text diff=python
|
||||
|
||||
# Binary files
|
||||
# ============
|
||||
*.db binary
|
||||
*.p binary
|
||||
*.pkl binary
|
||||
*.pickle binary
|
||||
*.pyc binary export-ignore
|
||||
*.pyo binary export-ignore
|
||||
*.pyd binary
|
||||
|
||||
# Jupyter notebook
|
||||
*.ipynb text eol=lf
|
||||
|
||||
# Note: .db, .p, and .pkl files are associated
|
||||
# with the python modules ``pickle``, ``dbm.*``,
|
||||
# ``shelve``, ``marshal``, ``anydbm``, & ``bsddb``
|
||||
# (among others).
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright © 2024 exec (https://github.com/exectails)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the “Software”), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,11 @@
|
||||
ComfyUI - String Utils
|
||||
=============================================================================
|
||||
|
||||
Collection of custom nodes for ComfyUI that make working with text
|
||||
easier, such as concatenating strings or composing new ones from
|
||||
parts.
|
||||
|
||||
Nodes
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
TODO
|
||||
@@ -0,0 +1,4 @@
|
||||
from .node import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./js"
|
||||
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|
||||
@@ -0,0 +1,129 @@
|
||||
from typing import Any
|
||||
|
||||
CATEGORY_NAME = "exectails"
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
any = AnyType("*")
|
||||
|
||||
|
||||
class ETTextFormatterNode:
|
||||
"""
|
||||
A node that takes a number of text inputs and concatenates them into
|
||||
a single string.
|
||||
"""
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("text",)
|
||||
|
||||
CATEGORY = CATEGORY_NAME
|
||||
FUNCTION = "process"
|
||||
|
||||
def process(self, format, arg0="", arg1="", arg2="", arg3="", arg4="", arg5="", arg6="", arg7="", arg8="", arg9="") -> tuple:
|
||||
# This looks pretty stupid, but it makes sense. I promise.
|
||||
# Technically, we just want to accept strings that we can
|
||||
# format, but due to how ComfyUI works, lists of strings
|
||||
# can be plugged into string inputs as if they were plain
|
||||
# strings.
|
||||
#
|
||||
# What happens then is that the system takes the length of
|
||||
# the longest list passed in, padding the other ones to the
|
||||
# same length, and calling the function for each entry. These
|
||||
# entries then make up the output list.
|
||||
#
|
||||
# To avoid this, you can set the input to be a list, process
|
||||
# will only be called once with all lists, and we can control
|
||||
# what the output will actually be. The caveat is that we need
|
||||
# do handle the lists somehow, so we flatten them to one string.
|
||||
# Easy!
|
||||
|
||||
format = self.flatten(format)
|
||||
arg0 = self.flatten(arg0)
|
||||
arg1 = self.flatten(arg1)
|
||||
arg2 = self.flatten(arg2)
|
||||
arg3 = self.flatten(arg3)
|
||||
arg4 = self.flatten(arg4)
|
||||
arg5 = self.flatten(arg5)
|
||||
arg6 = self.flatten(arg6)
|
||||
arg7 = self.flatten(arg7)
|
||||
arg8 = self.flatten(arg8)
|
||||
arg9 = self.flatten(arg9)
|
||||
|
||||
result = format.format(arg0=arg0, arg1=arg1, arg2=arg2, arg3=arg3, arg4=arg4, arg5=arg5, arg6=arg6, arg7=arg7, arg8=arg8, arg9=arg9)
|
||||
|
||||
return (result,)
|
||||
|
||||
def flatten(self, value: list) -> str:
|
||||
return " ".join([str(s) for s in value])
|
||||
|
||||
|
||||
class ETTextFormatter2Node(ETTextFormatterNode):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"format": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"arg0": ("STRING", {"forceInput": True}),
|
||||
"arg1": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ETTextFormatter5Node(ETTextFormatterNode):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"format": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"arg0": ("STRING", {"forceInput": True}),
|
||||
"arg1": ("STRING", {"forceInput": True}),
|
||||
"arg2": ("STRING", {"forceInput": True}),
|
||||
"arg3": ("STRING", {"forceInput": True}),
|
||||
"arg4": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ETTextFormatter10Node(ETTextFormatterNode):
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"format": ("STRING", {"multiline": True}),
|
||||
},
|
||||
"optional": {
|
||||
"arg0": ("STRING", {"forceInput": True}),
|
||||
"arg1": ("STRING", {"forceInput": True}),
|
||||
"arg2": ("STRING", {"forceInput": True}),
|
||||
"arg3": ("STRING", {"forceInput": True}),
|
||||
"arg4": ("STRING", {"forceInput": True}),
|
||||
"arg5": ("STRING", {"forceInput": True}),
|
||||
"arg6": ("STRING", {"forceInput": True}),
|
||||
"arg7": ("STRING", {"forceInput": True}),
|
||||
"arg8": ("STRING", {"forceInput": True}),
|
||||
"arg9": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ETTextFormatter2Node": ETTextFormatter2Node,
|
||||
"ETTextFormatter5Node": ETTextFormatter5Node,
|
||||
"ETTextFormatter10Node": ETTextFormatter10Node,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ETTextFormatter2Node": "Text Formatter (2 Arguments)",
|
||||
"ETTextFormatter5Node": "Text Formatter (5 Arguments)",
|
||||
"ETTextFormatter10Node": "Text Formatter (10 Arguments)",
|
||||
}
|
||||
Reference in New Issue
Block a user