Merge branch 'develop' of github.com:Tegiminis/evennia into develop
# Conflicts: # evennia/typeclasses/attributes.py
This commit is contained in:
commit
da0e380fa5
73 changed files with 2774 additions and 1099 deletions
|
|
@ -162,6 +162,12 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10
|
||||||
way to override features on all ObjectDB-inheriting objects easily.
|
way to override features on all ObjectDB-inheriting objects easily.
|
||||||
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
|
- Add `TagProperty`, `AliasProperty` and `PermissionProperty` to assign these
|
||||||
data in a similar way to django fields.
|
data in a similar way to django fields.
|
||||||
|
- The db pickle-serializer now checks for methods `__serialize_dbobjs__` and `__deserialize_dbobjs__`
|
||||||
|
to allow custom packing/unpacking of nested dbobjs, to allow storing in Attribute.
|
||||||
|
- Optimizations to rpsystem contrib performance. Breaking change: `.get_sdesc()` will
|
||||||
|
now return `None` instead of `.db.desc` if no sdesc is set; fallback in hook (inspectorCaracal)
|
||||||
|
- Reworked text2html parser to avoid problems with stateful color tags (inspectorCaracal)
|
||||||
|
- Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal)
|
||||||
|
|
||||||
|
|
||||||
## Evennia 0.9.5
|
## Evennia 0.9.5
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ or in the chat.
|
||||||
|
|
||||||
[pep8]: http://www.python.org/dev/peps/pep-0008
|
[pep8]: http://www.python.org/dev/peps/pep-0008
|
||||||
[pep8tool]: https://pypi.python.org/pypi/pep8
|
[pep8tool]: https://pypi.python.org/pypi/pep8
|
||||||
[googlestyle]: http://www.sphinx-doc.org/en/stable/ext/example_google.html
|
[googlestyle]: https://www.sphinx-doc.org/en/master/usage/extensions/example_google.html
|
||||||
[githubmarkdown]: https://help.github.com/articles/github-flavored-markdown/
|
[githubmarkdown]: https://help.github.com/articles/github-flavored-markdown/
|
||||||
[markdown-hilight]: https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting
|
[markdown-hilight]: https://help.github.com/articles/github-flavored-markdown/#syntax-highlighting
|
||||||
[command-docstrings]: https://github.com/evennia/evennia/wiki/Using%20MUX%20As%20a%20Standard#documentation-policy
|
[command-docstrings]: https://github.com/evennia/evennia/wiki/Using%20MUX%20As%20a%20Standard#documentation-policy
|
||||||
|
|
|
||||||
16
README.md
16
README.md
|
|
@ -60,22 +60,22 @@ introduction][introduction] to read.
|
||||||
|
|
||||||
To learn how to get your hands on the code base, the [Getting
|
To learn how to get your hands on the code base, the [Getting
|
||||||
started][gettingstarted] page is the way to go. Otherwise you could
|
started][gettingstarted] page is the way to go. Otherwise you could
|
||||||
browse the [Documentation][wiki] or why not come join the [Evennia
|
browse the [Documentation][docs] or why not come join the [Evennia
|
||||||
Community forum][group] or join us in our [development chat][chat].
|
Community forum][group] or join us in our [development chat][chat].
|
||||||
Welcome!
|
Welcome!
|
||||||
|
|
||||||
|
|
||||||
[homepage]: http://www.evennia.com
|
[homepage]: https://www.evennia.com
|
||||||
[gettingstarted]: http://github.com/evennia/evennia/wiki/Getting-Started
|
[gettingstarted]: https://www.evennia.com/docs/latest/Getting-Started.html
|
||||||
[wiki]: https://github.com/evennia/evennia/wiki
|
[docs]: https://www.evennia.com/docs/latest
|
||||||
[screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png
|
[screenshot]: https://user-images.githubusercontent.com/294267/30773728-ea45afb6-a076-11e7-8820-49be2168a6b8.png
|
||||||
[logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png
|
[logo]: https://github.com/evennia/evennia/blob/master/evennia/web/website/static/website/images/evennia_logo.png
|
||||||
[unittestciimg]: https://github.com/evennia/evennia/workflows/test-suite/badge.svg
|
[unittestciimg]: https://github.com/evennia/evennia/workflows/test-suite/badge.svg
|
||||||
[unittestcilink]: https://github.com/evennia/evennia/actions?query=workflow%3Atest-suite
|
[unittestcilink]: https://github.com/evennia/evennia/actions?query=workflow%3Atest-suite
|
||||||
[coverimg]: https://coveralls.io/repos/github/evennia/evennia/badge.svg?branch=master
|
[coverimg]: https://coveralls.io/repos/github/evennia/evennia/badge.svg?branch=master
|
||||||
[coverlink]: https://coveralls.io/github/evennia/evennia?branch=master
|
[coverlink]: https://coveralls.io/github/evennia/evennia?branch=master
|
||||||
[introduction]: https://github.com/evennia/evennia/wiki/Evennia-Introduction
|
[introduction]: https://www.evennia.com/docs/latest/Evennia-Introduction.html
|
||||||
[license]: https://github.com/evennia/evennia/wiki/Licensing
|
[license]: https://www.evennia.com/docs/latest/Licensing.html
|
||||||
[group]: https://groups.google.com/forum/#!forum/evennia
|
[group]: https://github.com/evennia/evennia/discussions
|
||||||
[chat]: http://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb
|
[chat]: https://discord.gg/AJJpcRUhtF
|
||||||
[wikimudpage]: http://en.wikipedia.org/wiki/MUD
|
[wikimudpage]: http://en.wikipedia.org/wiki/MUD
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@ mv-local:
|
||||||
@echo "Documentation built (multiversion + autodocs)."
|
@echo "Documentation built (multiversion + autodocs)."
|
||||||
@echo "To see result, open evennia/docs/build/html/<version>/index.html in a browser."
|
@echo "To see result, open evennia/docs/build/html/<version>/index.html in a browser."
|
||||||
|
|
||||||
|
# note - don't run deploy/release manually, the result will clash with the
|
||||||
|
# result of the github actions!
|
||||||
deploy:
|
deploy:
|
||||||
make _multiversion-deploy
|
make _multiversion-deploy
|
||||||
@echo "Documentation deployed."
|
@echo "Documentation deployed."
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ git checkout gh-pages
|
||||||
# with the build/ directory available since this is not in git
|
# with the build/ directory available since this is not in git
|
||||||
|
|
||||||
# remove all but the build dir
|
# remove all but the build dir
|
||||||
|
# TODO don't delete old branches after 1.0 release; they will get harder and harder to rebuild
|
||||||
ls -Q | grep -v build | xargs rm -Rf
|
ls -Q | grep -v build | xargs rm -Rf
|
||||||
|
|
||||||
cp -Rf build/html/* .
|
cp -Rf build/html/* .
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
sphinx==3.2.1
|
sphinx==3.2.1
|
||||||
myst-parser==0.15.2
|
myst-parser==0.15.2
|
||||||
myst-parser[linkify]==0.15.2
|
myst-parser[linkify]==0.15.2
|
||||||
|
Jinja2 < 3.1
|
||||||
|
|
||||||
# sphinx-multiversion with evennia fixes
|
# sphinx-multiversion with evennia fixes
|
||||||
git+https://github.com/evennia/sphinx-multiversion.git@evennia-mods#egg=sphinx-multiversion
|
git+https://github.com/evennia/sphinx-multiversion.git@evennia-mods#egg=sphinx-multiversion
|
||||||
|
|
|
||||||
8
docs/source/.vale.ini
Normal file
8
docs/source/.vale.ini
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
StylesPath = .vale
|
||||||
|
|
||||||
|
Vocab = docs
|
||||||
|
Packages = write-good
|
||||||
|
MinAlertLevel = error
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
BasedOnStyles = Vale, write-good
|
||||||
19
docs/source/.vale/Vocab/docs/accept.txt
Normal file
19
docs/source/.vale/Vocab/docs/accept.txt
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Evennia
|
||||||
|
Pastebin
|
||||||
|
[Cc]ontrib(s)*
|
||||||
|
Patreon
|
||||||
|
[Rr]epo(s)*
|
||||||
|
(?i)readme
|
||||||
|
[Ss]ubfolder(s)*
|
||||||
|
[Dd]ev(s)*
|
||||||
|
Github
|
||||||
|
[Dd]ocstring(s)*
|
||||||
|
[Mm]ygame(s)*
|
||||||
|
[Gg]amedir(s)*
|
||||||
|
[Vv]irtualenv(s)*
|
||||||
|
Python
|
||||||
|
API
|
||||||
|
[Tt]ypeclass(es)*?
|
||||||
|
[Bb]ullet point(s)*
|
||||||
|
CommonMark
|
||||||
|
[Pp]reparser(s)*
|
||||||
0
docs/source/.vale/Vocab/docs/reject.txt
Normal file
0
docs/source/.vale/Vocab/docs/reject.txt
Normal file
702
docs/source/.vale/write-good/Cliches.yml
Normal file
702
docs/source/.vale/write-good/Cliches.yml
Normal file
|
|
@ -0,0 +1,702 @@
|
||||||
|
extends: existence
|
||||||
|
message: "Try to avoid using clichés like '%s'."
|
||||||
|
ignorecase: true
|
||||||
|
level: warning
|
||||||
|
tokens:
|
||||||
|
- a chip off the old block
|
||||||
|
- a clean slate
|
||||||
|
- a dark and stormy night
|
||||||
|
- a far cry
|
||||||
|
- a fine kettle of fish
|
||||||
|
- a loose cannon
|
||||||
|
- a penny saved is a penny earned
|
||||||
|
- a tough row to hoe
|
||||||
|
- a word to the wise
|
||||||
|
- ace in the hole
|
||||||
|
- acid test
|
||||||
|
- add insult to injury
|
||||||
|
- against all odds
|
||||||
|
- air your dirty laundry
|
||||||
|
- all fun and games
|
||||||
|
- all in a day's work
|
||||||
|
- all talk, no action
|
||||||
|
- all thumbs
|
||||||
|
- all your eggs in one basket
|
||||||
|
- all's fair in love and war
|
||||||
|
- all's well that ends well
|
||||||
|
- almighty dollar
|
||||||
|
- American as apple pie
|
||||||
|
- an axe to grind
|
||||||
|
- another day, another dollar
|
||||||
|
- armed to the teeth
|
||||||
|
- as luck would have it
|
||||||
|
- as old as time
|
||||||
|
- as the crow flies
|
||||||
|
- at loose ends
|
||||||
|
- at my wits end
|
||||||
|
- avoid like the plague
|
||||||
|
- babe in the woods
|
||||||
|
- back against the wall
|
||||||
|
- back in the saddle
|
||||||
|
- back to square one
|
||||||
|
- back to the drawing board
|
||||||
|
- bad to the bone
|
||||||
|
- badge of honor
|
||||||
|
- bald faced liar
|
||||||
|
- ballpark figure
|
||||||
|
- banging your head against a brick wall
|
||||||
|
- baptism by fire
|
||||||
|
- barking up the wrong tree
|
||||||
|
- bat out of hell
|
||||||
|
- be all and end all
|
||||||
|
- beat a dead horse
|
||||||
|
- beat around the bush
|
||||||
|
- been there, done that
|
||||||
|
- beggars can't be choosers
|
||||||
|
- behind the eight ball
|
||||||
|
- bend over backwards
|
||||||
|
- benefit of the doubt
|
||||||
|
- bent out of shape
|
||||||
|
- best thing since sliced bread
|
||||||
|
- bet your bottom dollar
|
||||||
|
- better half
|
||||||
|
- better late than never
|
||||||
|
- better mousetrap
|
||||||
|
- better safe than sorry
|
||||||
|
- between a rock and a hard place
|
||||||
|
- beyond the pale
|
||||||
|
- bide your time
|
||||||
|
- big as life
|
||||||
|
- big cheese
|
||||||
|
- big fish in a small pond
|
||||||
|
- big man on campus
|
||||||
|
- bigger they are the harder they fall
|
||||||
|
- bird in the hand
|
||||||
|
- bird's eye view
|
||||||
|
- birds and the bees
|
||||||
|
- birds of a feather flock together
|
||||||
|
- bit the hand that feeds you
|
||||||
|
- bite the bullet
|
||||||
|
- bite the dust
|
||||||
|
- bitten off more than he can chew
|
||||||
|
- black as coal
|
||||||
|
- black as pitch
|
||||||
|
- black as the ace of spades
|
||||||
|
- blast from the past
|
||||||
|
- bleeding heart
|
||||||
|
- blessing in disguise
|
||||||
|
- blind ambition
|
||||||
|
- blind as a bat
|
||||||
|
- blind leading the blind
|
||||||
|
- blood is thicker than water
|
||||||
|
- blood sweat and tears
|
||||||
|
- blow off steam
|
||||||
|
- blow your own horn
|
||||||
|
- blushing bride
|
||||||
|
- boils down to
|
||||||
|
- bolt from the blue
|
||||||
|
- bone to pick
|
||||||
|
- bored stiff
|
||||||
|
- bored to tears
|
||||||
|
- bottomless pit
|
||||||
|
- boys will be boys
|
||||||
|
- bright and early
|
||||||
|
- brings home the bacon
|
||||||
|
- broad across the beam
|
||||||
|
- broken record
|
||||||
|
- brought back to reality
|
||||||
|
- bull by the horns
|
||||||
|
- bull in a china shop
|
||||||
|
- burn the midnight oil
|
||||||
|
- burning question
|
||||||
|
- burning the candle at both ends
|
||||||
|
- burst your bubble
|
||||||
|
- bury the hatchet
|
||||||
|
- busy as a bee
|
||||||
|
- by hook or by crook
|
||||||
|
- call a spade a spade
|
||||||
|
- called onto the carpet
|
||||||
|
- calm before the storm
|
||||||
|
- can of worms
|
||||||
|
- can't cut the mustard
|
||||||
|
- can't hold a candle to
|
||||||
|
- case of mistaken identity
|
||||||
|
- cat got your tongue
|
||||||
|
- cat's meow
|
||||||
|
- caught in the crossfire
|
||||||
|
- caught red-handed
|
||||||
|
- checkered past
|
||||||
|
- chomping at the bit
|
||||||
|
- cleanliness is next to godliness
|
||||||
|
- clear as a bell
|
||||||
|
- clear as mud
|
||||||
|
- close to the vest
|
||||||
|
- cock and bull story
|
||||||
|
- cold shoulder
|
||||||
|
- come hell or high water
|
||||||
|
- cool as a cucumber
|
||||||
|
- cool, calm, and collected
|
||||||
|
- cost a king's ransom
|
||||||
|
- count your blessings
|
||||||
|
- crack of dawn
|
||||||
|
- crash course
|
||||||
|
- creature comforts
|
||||||
|
- cross that bridge when you come to it
|
||||||
|
- crushing blow
|
||||||
|
- cry like a baby
|
||||||
|
- cry me a river
|
||||||
|
- cry over spilt milk
|
||||||
|
- crystal clear
|
||||||
|
- curiosity killed the cat
|
||||||
|
- cut and dried
|
||||||
|
- cut through the red tape
|
||||||
|
- cut to the chase
|
||||||
|
- cute as a bugs ear
|
||||||
|
- cute as a button
|
||||||
|
- cute as a puppy
|
||||||
|
- cuts to the quick
|
||||||
|
- dark before the dawn
|
||||||
|
- day in, day out
|
||||||
|
- dead as a doornail
|
||||||
|
- devil is in the details
|
||||||
|
- dime a dozen
|
||||||
|
- divide and conquer
|
||||||
|
- dog and pony show
|
||||||
|
- dog days
|
||||||
|
- dog eat dog
|
||||||
|
- dog tired
|
||||||
|
- don't burn your bridges
|
||||||
|
- don't count your chickens
|
||||||
|
- don't look a gift horse in the mouth
|
||||||
|
- don't rock the boat
|
||||||
|
- don't step on anyone's toes
|
||||||
|
- don't take any wooden nickels
|
||||||
|
- down and out
|
||||||
|
- down at the heels
|
||||||
|
- down in the dumps
|
||||||
|
- down the hatch
|
||||||
|
- down to earth
|
||||||
|
- draw the line
|
||||||
|
- dressed to kill
|
||||||
|
- dressed to the nines
|
||||||
|
- drives me up the wall
|
||||||
|
- dull as dishwater
|
||||||
|
- dyed in the wool
|
||||||
|
- eagle eye
|
||||||
|
- ear to the ground
|
||||||
|
- early bird catches the worm
|
||||||
|
- easier said than done
|
||||||
|
- easy as pie
|
||||||
|
- eat your heart out
|
||||||
|
- eat your words
|
||||||
|
- eleventh hour
|
||||||
|
- even the playing field
|
||||||
|
- every dog has its day
|
||||||
|
- every fiber of my being
|
||||||
|
- everything but the kitchen sink
|
||||||
|
- eye for an eye
|
||||||
|
- face the music
|
||||||
|
- facts of life
|
||||||
|
- fair weather friend
|
||||||
|
- fall by the wayside
|
||||||
|
- fan the flames
|
||||||
|
- feast or famine
|
||||||
|
- feather your nest
|
||||||
|
- feathered friends
|
||||||
|
- few and far between
|
||||||
|
- fifteen minutes of fame
|
||||||
|
- filthy vermin
|
||||||
|
- fine kettle of fish
|
||||||
|
- fish out of water
|
||||||
|
- fishing for a compliment
|
||||||
|
- fit as a fiddle
|
||||||
|
- fit the bill
|
||||||
|
- fit to be tied
|
||||||
|
- flash in the pan
|
||||||
|
- flat as a pancake
|
||||||
|
- flip your lid
|
||||||
|
- flog a dead horse
|
||||||
|
- fly by night
|
||||||
|
- fly the coop
|
||||||
|
- follow your heart
|
||||||
|
- for all intents and purposes
|
||||||
|
- for the birds
|
||||||
|
- for what it's worth
|
||||||
|
- force of nature
|
||||||
|
- force to be reckoned with
|
||||||
|
- forgive and forget
|
||||||
|
- fox in the henhouse
|
||||||
|
- free and easy
|
||||||
|
- free as a bird
|
||||||
|
- fresh as a daisy
|
||||||
|
- full steam ahead
|
||||||
|
- fun in the sun
|
||||||
|
- garbage in, garbage out
|
||||||
|
- gentle as a lamb
|
||||||
|
- get a kick out of
|
||||||
|
- get a leg up
|
||||||
|
- get down and dirty
|
||||||
|
- get the lead out
|
||||||
|
- get to the bottom of
|
||||||
|
- get your feet wet
|
||||||
|
- gets my goat
|
||||||
|
- gilding the lily
|
||||||
|
- give and take
|
||||||
|
- go against the grain
|
||||||
|
- go at it tooth and nail
|
||||||
|
- go for broke
|
||||||
|
- go him one better
|
||||||
|
- go the extra mile
|
||||||
|
- go with the flow
|
||||||
|
- goes without saying
|
||||||
|
- good as gold
|
||||||
|
- good deed for the day
|
||||||
|
- good things come to those who wait
|
||||||
|
- good time was had by all
|
||||||
|
- good times were had by all
|
||||||
|
- greased lightning
|
||||||
|
- greek to me
|
||||||
|
- green thumb
|
||||||
|
- green-eyed monster
|
||||||
|
- grist for the mill
|
||||||
|
- growing like a weed
|
||||||
|
- hair of the dog
|
||||||
|
- hand to mouth
|
||||||
|
- happy as a clam
|
||||||
|
- happy as a lark
|
||||||
|
- hasn't a clue
|
||||||
|
- have a nice day
|
||||||
|
- have high hopes
|
||||||
|
- have the last laugh
|
||||||
|
- haven't got a row to hoe
|
||||||
|
- head honcho
|
||||||
|
- head over heels
|
||||||
|
- hear a pin drop
|
||||||
|
- heard it through the grapevine
|
||||||
|
- heart's content
|
||||||
|
- heavy as lead
|
||||||
|
- hem and haw
|
||||||
|
- high and dry
|
||||||
|
- high and mighty
|
||||||
|
- high as a kite
|
||||||
|
- hit paydirt
|
||||||
|
- hold your head up high
|
||||||
|
- hold your horses
|
||||||
|
- hold your own
|
||||||
|
- hold your tongue
|
||||||
|
- honest as the day is long
|
||||||
|
- horns of a dilemma
|
||||||
|
- horse of a different color
|
||||||
|
- hot under the collar
|
||||||
|
- hour of need
|
||||||
|
- I beg to differ
|
||||||
|
- icing on the cake
|
||||||
|
- if the shoe fits
|
||||||
|
- if the shoe were on the other foot
|
||||||
|
- in a jam
|
||||||
|
- in a jiffy
|
||||||
|
- in a nutshell
|
||||||
|
- in a pig's eye
|
||||||
|
- in a pinch
|
||||||
|
- in a word
|
||||||
|
- in hot water
|
||||||
|
- in the gutter
|
||||||
|
- in the nick of time
|
||||||
|
- in the thick of it
|
||||||
|
- in your dreams
|
||||||
|
- it ain't over till the fat lady sings
|
||||||
|
- it goes without saying
|
||||||
|
- it takes all kinds
|
||||||
|
- it takes one to know one
|
||||||
|
- it's a small world
|
||||||
|
- it's only a matter of time
|
||||||
|
- ivory tower
|
||||||
|
- Jack of all trades
|
||||||
|
- jockey for position
|
||||||
|
- jog your memory
|
||||||
|
- joined at the hip
|
||||||
|
- judge a book by its cover
|
||||||
|
- jump down your throat
|
||||||
|
- jump in with both feet
|
||||||
|
- jump on the bandwagon
|
||||||
|
- jump the gun
|
||||||
|
- jump to conclusions
|
||||||
|
- just a hop, skip, and a jump
|
||||||
|
- just the ticket
|
||||||
|
- justice is blind
|
||||||
|
- keep a stiff upper lip
|
||||||
|
- keep an eye on
|
||||||
|
- keep it simple, stupid
|
||||||
|
- keep the home fires burning
|
||||||
|
- keep up with the Joneses
|
||||||
|
- keep your chin up
|
||||||
|
- keep your fingers crossed
|
||||||
|
- kick the bucket
|
||||||
|
- kick up your heels
|
||||||
|
- kick your feet up
|
||||||
|
- kid in a candy store
|
||||||
|
- kill two birds with one stone
|
||||||
|
- kiss of death
|
||||||
|
- knock it out of the park
|
||||||
|
- knock on wood
|
||||||
|
- knock your socks off
|
||||||
|
- know him from Adam
|
||||||
|
- know the ropes
|
||||||
|
- know the score
|
||||||
|
- knuckle down
|
||||||
|
- knuckle sandwich
|
||||||
|
- knuckle under
|
||||||
|
- labor of love
|
||||||
|
- ladder of success
|
||||||
|
- land on your feet
|
||||||
|
- lap of luxury
|
||||||
|
- last but not least
|
||||||
|
- last hurrah
|
||||||
|
- last-ditch effort
|
||||||
|
- law of the jungle
|
||||||
|
- law of the land
|
||||||
|
- lay down the law
|
||||||
|
- leaps and bounds
|
||||||
|
- let sleeping dogs lie
|
||||||
|
- let the cat out of the bag
|
||||||
|
- let the good times roll
|
||||||
|
- let your hair down
|
||||||
|
- let's talk turkey
|
||||||
|
- letter perfect
|
||||||
|
- lick your wounds
|
||||||
|
- lies like a rug
|
||||||
|
- life's a bitch
|
||||||
|
- life's a grind
|
||||||
|
- light at the end of the tunnel
|
||||||
|
- lighter than a feather
|
||||||
|
- lighter than air
|
||||||
|
- like clockwork
|
||||||
|
- like father like son
|
||||||
|
- like taking candy from a baby
|
||||||
|
- like there's no tomorrow
|
||||||
|
- lion's share
|
||||||
|
- live and learn
|
||||||
|
- live and let live
|
||||||
|
- long and short of it
|
||||||
|
- long lost love
|
||||||
|
- look before you leap
|
||||||
|
- look down your nose
|
||||||
|
- look what the cat dragged in
|
||||||
|
- looking a gift horse in the mouth
|
||||||
|
- looks like death warmed over
|
||||||
|
- loose cannon
|
||||||
|
- lose your head
|
||||||
|
- lose your temper
|
||||||
|
- loud as a horn
|
||||||
|
- lounge lizard
|
||||||
|
- loved and lost
|
||||||
|
- low man on the totem pole
|
||||||
|
- luck of the draw
|
||||||
|
- luck of the Irish
|
||||||
|
- make hay while the sun shines
|
||||||
|
- make money hand over fist
|
||||||
|
- make my day
|
||||||
|
- make the best of a bad situation
|
||||||
|
- make the best of it
|
||||||
|
- make your blood boil
|
||||||
|
- man of few words
|
||||||
|
- man's best friend
|
||||||
|
- mark my words
|
||||||
|
- meaningful dialogue
|
||||||
|
- missed the boat on that one
|
||||||
|
- moment in the sun
|
||||||
|
- moment of glory
|
||||||
|
- moment of truth
|
||||||
|
- money to burn
|
||||||
|
- more power to you
|
||||||
|
- more than one way to skin a cat
|
||||||
|
- movers and shakers
|
||||||
|
- moving experience
|
||||||
|
- naked as a jaybird
|
||||||
|
- naked truth
|
||||||
|
- neat as a pin
|
||||||
|
- needle in a haystack
|
||||||
|
- needless to say
|
||||||
|
- neither here nor there
|
||||||
|
- never look back
|
||||||
|
- never say never
|
||||||
|
- nip and tuck
|
||||||
|
- nip it in the bud
|
||||||
|
- no guts, no glory
|
||||||
|
- no love lost
|
||||||
|
- no pain, no gain
|
||||||
|
- no skin off my back
|
||||||
|
- no stone unturned
|
||||||
|
- no time like the present
|
||||||
|
- no use crying over spilled milk
|
||||||
|
- nose to the grindstone
|
||||||
|
- not a hope in hell
|
||||||
|
- not a minute's peace
|
||||||
|
- not in my backyard
|
||||||
|
- not playing with a full deck
|
||||||
|
- not the end of the world
|
||||||
|
- not written in stone
|
||||||
|
- nothing to sneeze at
|
||||||
|
- nothing ventured nothing gained
|
||||||
|
- now we're cooking
|
||||||
|
- off the top of my head
|
||||||
|
- off the wagon
|
||||||
|
- off the wall
|
||||||
|
- old hat
|
||||||
|
- older and wiser
|
||||||
|
- older than dirt
|
||||||
|
- older than Methuselah
|
||||||
|
- on a roll
|
||||||
|
- on cloud nine
|
||||||
|
- on pins and needles
|
||||||
|
- on the bandwagon
|
||||||
|
- on the money
|
||||||
|
- on the nose
|
||||||
|
- on the rocks
|
||||||
|
- on the spot
|
||||||
|
- on the tip of my tongue
|
||||||
|
- on the wagon
|
||||||
|
- on thin ice
|
||||||
|
- once bitten, twice shy
|
||||||
|
- one bad apple doesn't spoil the bushel
|
||||||
|
- one born every minute
|
||||||
|
- one brick short
|
||||||
|
- one foot in the grave
|
||||||
|
- one in a million
|
||||||
|
- one red cent
|
||||||
|
- only game in town
|
||||||
|
- open a can of worms
|
||||||
|
- open and shut case
|
||||||
|
- open the flood gates
|
||||||
|
- opportunity doesn't knock twice
|
||||||
|
- out of pocket
|
||||||
|
- out of sight, out of mind
|
||||||
|
- out of the frying pan into the fire
|
||||||
|
- out of the woods
|
||||||
|
- out on a limb
|
||||||
|
- over a barrel
|
||||||
|
- over the hump
|
||||||
|
- pain and suffering
|
||||||
|
- pain in the
|
||||||
|
- panic button
|
||||||
|
- par for the course
|
||||||
|
- part and parcel
|
||||||
|
- party pooper
|
||||||
|
- pass the buck
|
||||||
|
- patience is a virtue
|
||||||
|
- pay through the nose
|
||||||
|
- penny pincher
|
||||||
|
- perfect storm
|
||||||
|
- pig in a poke
|
||||||
|
- pile it on
|
||||||
|
- pillar of the community
|
||||||
|
- pin your hopes on
|
||||||
|
- pitter patter of little feet
|
||||||
|
- plain as day
|
||||||
|
- plain as the nose on your face
|
||||||
|
- play by the rules
|
||||||
|
- play your cards right
|
||||||
|
- playing the field
|
||||||
|
- playing with fire
|
||||||
|
- pleased as punch
|
||||||
|
- plenty of fish in the sea
|
||||||
|
- point with pride
|
||||||
|
- poor as a church mouse
|
||||||
|
- pot calling the kettle black
|
||||||
|
- pretty as a picture
|
||||||
|
- pull a fast one
|
||||||
|
- pull your punches
|
||||||
|
- pulling your leg
|
||||||
|
- pure as the driven snow
|
||||||
|
- put it in a nutshell
|
||||||
|
- put one over on you
|
||||||
|
- put the cart before the horse
|
||||||
|
- put the pedal to the metal
|
||||||
|
- put your best foot forward
|
||||||
|
- put your foot down
|
||||||
|
- quick as a bunny
|
||||||
|
- quick as a lick
|
||||||
|
- quick as a wink
|
||||||
|
- quick as lightning
|
||||||
|
- quiet as a dormouse
|
||||||
|
- rags to riches
|
||||||
|
- raining buckets
|
||||||
|
- raining cats and dogs
|
||||||
|
- rank and file
|
||||||
|
- rat race
|
||||||
|
- reap what you sow
|
||||||
|
- red as a beet
|
||||||
|
- red herring
|
||||||
|
- reinvent the wheel
|
||||||
|
- rich and famous
|
||||||
|
- rings a bell
|
||||||
|
- ripe old age
|
||||||
|
- ripped me off
|
||||||
|
- rise and shine
|
||||||
|
- road to hell is paved with good intentions
|
||||||
|
- rob Peter to pay Paul
|
||||||
|
- roll over in the grave
|
||||||
|
- rub the wrong way
|
||||||
|
- ruled the roost
|
||||||
|
- running in circles
|
||||||
|
- sad but true
|
||||||
|
- sadder but wiser
|
||||||
|
- salt of the earth
|
||||||
|
- scared stiff
|
||||||
|
- scared to death
|
||||||
|
- sealed with a kiss
|
||||||
|
- second to none
|
||||||
|
- see eye to eye
|
||||||
|
- seen the light
|
||||||
|
- seize the day
|
||||||
|
- set the record straight
|
||||||
|
- set the world on fire
|
||||||
|
- set your teeth on edge
|
||||||
|
- sharp as a tack
|
||||||
|
- shoot for the moon
|
||||||
|
- shoot the breeze
|
||||||
|
- shot in the dark
|
||||||
|
- shoulder to the wheel
|
||||||
|
- sick as a dog
|
||||||
|
- sigh of relief
|
||||||
|
- signed, sealed, and delivered
|
||||||
|
- sink or swim
|
||||||
|
- six of one, half a dozen of another
|
||||||
|
- skating on thin ice
|
||||||
|
- slept like a log
|
||||||
|
- slinging mud
|
||||||
|
- slippery as an eel
|
||||||
|
- slow as molasses
|
||||||
|
- smart as a whip
|
||||||
|
- smooth as a baby's bottom
|
||||||
|
- sneaking suspicion
|
||||||
|
- snug as a bug in a rug
|
||||||
|
- sow wild oats
|
||||||
|
- spare the rod, spoil the child
|
||||||
|
- speak of the devil
|
||||||
|
- spilled the beans
|
||||||
|
- spinning your wheels
|
||||||
|
- spitting image of
|
||||||
|
- spoke with relish
|
||||||
|
- spread like wildfire
|
||||||
|
- spring to life
|
||||||
|
- squeaky wheel gets the grease
|
||||||
|
- stands out like a sore thumb
|
||||||
|
- start from scratch
|
||||||
|
- stick in the mud
|
||||||
|
- still waters run deep
|
||||||
|
- stitch in time
|
||||||
|
- stop and smell the roses
|
||||||
|
- straight as an arrow
|
||||||
|
- straw that broke the camel's back
|
||||||
|
- strong as an ox
|
||||||
|
- stubborn as a mule
|
||||||
|
- stuff that dreams are made of
|
||||||
|
- stuffed shirt
|
||||||
|
- sweating blood
|
||||||
|
- sweating bullets
|
||||||
|
- take a load off
|
||||||
|
- take one for the team
|
||||||
|
- take the bait
|
||||||
|
- take the bull by the horns
|
||||||
|
- take the plunge
|
||||||
|
- takes one to know one
|
||||||
|
- takes two to tango
|
||||||
|
- the more the merrier
|
||||||
|
- the real deal
|
||||||
|
- the real McCoy
|
||||||
|
- the red carpet treatment
|
||||||
|
- the same old story
|
||||||
|
- there is no accounting for taste
|
||||||
|
- thick as a brick
|
||||||
|
- thick as thieves
|
||||||
|
- thin as a rail
|
||||||
|
- think outside of the box
|
||||||
|
- third time's the charm
|
||||||
|
- this day and age
|
||||||
|
- this hurts me worse than it hurts you
|
||||||
|
- this point in time
|
||||||
|
- three sheets to the wind
|
||||||
|
- through thick and thin
|
||||||
|
- throw in the towel
|
||||||
|
- tie one on
|
||||||
|
- tighter than a drum
|
||||||
|
- time and time again
|
||||||
|
- time is of the essence
|
||||||
|
- tip of the iceberg
|
||||||
|
- tired but happy
|
||||||
|
- to coin a phrase
|
||||||
|
- to each his own
|
||||||
|
- to make a long story short
|
||||||
|
- to the best of my knowledge
|
||||||
|
- toe the line
|
||||||
|
- tongue in cheek
|
||||||
|
- too good to be true
|
||||||
|
- too hot to handle
|
||||||
|
- too numerous to mention
|
||||||
|
- touch with a ten foot pole
|
||||||
|
- tough as nails
|
||||||
|
- trial and error
|
||||||
|
- trials and tribulations
|
||||||
|
- tried and true
|
||||||
|
- trip down memory lane
|
||||||
|
- twist of fate
|
||||||
|
- two cents worth
|
||||||
|
- two peas in a pod
|
||||||
|
- ugly as sin
|
||||||
|
- under the counter
|
||||||
|
- under the gun
|
||||||
|
- under the same roof
|
||||||
|
- under the weather
|
||||||
|
- until the cows come home
|
||||||
|
- unvarnished truth
|
||||||
|
- up the creek
|
||||||
|
- uphill battle
|
||||||
|
- upper crust
|
||||||
|
- upset the applecart
|
||||||
|
- vain attempt
|
||||||
|
- vain effort
|
||||||
|
- vanquish the enemy
|
||||||
|
- vested interest
|
||||||
|
- waiting for the other shoe to drop
|
||||||
|
- wakeup call
|
||||||
|
- warm welcome
|
||||||
|
- watch your p's and q's
|
||||||
|
- watch your tongue
|
||||||
|
- watching the clock
|
||||||
|
- water under the bridge
|
||||||
|
- weather the storm
|
||||||
|
- weed them out
|
||||||
|
- week of Sundays
|
||||||
|
- went belly up
|
||||||
|
- wet behind the ears
|
||||||
|
- what goes around comes around
|
||||||
|
- what you see is what you get
|
||||||
|
- when it rains, it pours
|
||||||
|
- when push comes to shove
|
||||||
|
- when the cat's away
|
||||||
|
- when the going gets tough, the tough get going
|
||||||
|
- white as a sheet
|
||||||
|
- whole ball of wax
|
||||||
|
- whole hog
|
||||||
|
- whole nine yards
|
||||||
|
- wild goose chase
|
||||||
|
- will wonders never cease?
|
||||||
|
- wisdom of the ages
|
||||||
|
- wise as an owl
|
||||||
|
- wolf at the door
|
||||||
|
- words fail me
|
||||||
|
- work like a dog
|
||||||
|
- world weary
|
||||||
|
- worst nightmare
|
||||||
|
- worth its weight in gold
|
||||||
|
- wrong side of the bed
|
||||||
|
- yanking your chain
|
||||||
|
- yappy as a dog
|
||||||
|
- years young
|
||||||
|
- you are what you eat
|
||||||
|
- you can run but you can't hide
|
||||||
|
- you only live once
|
||||||
|
- you're the boss
|
||||||
|
- young and foolish
|
||||||
|
- young and vibrant
|
||||||
32
docs/source/.vale/write-good/E-Prime.yml
Normal file
32
docs/source/.vale/write-good/E-Prime.yml
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
extends: existence
|
||||||
|
message: "Try to avoid using '%s'."
|
||||||
|
ignorecase: true
|
||||||
|
level: suggestion
|
||||||
|
tokens:
|
||||||
|
- am
|
||||||
|
- are
|
||||||
|
- aren't
|
||||||
|
- be
|
||||||
|
- been
|
||||||
|
- being
|
||||||
|
- he's
|
||||||
|
- here's
|
||||||
|
- here's
|
||||||
|
- how's
|
||||||
|
- i'm
|
||||||
|
- is
|
||||||
|
- isn't
|
||||||
|
- it's
|
||||||
|
- she's
|
||||||
|
- that's
|
||||||
|
- there's
|
||||||
|
- they're
|
||||||
|
- was
|
||||||
|
- wasn't
|
||||||
|
- we're
|
||||||
|
- were
|
||||||
|
- weren't
|
||||||
|
- what's
|
||||||
|
- where's
|
||||||
|
- who's
|
||||||
|
- you're
|
||||||
11
docs/source/.vale/write-good/Illusions.yml
Normal file
11
docs/source/.vale/write-good/Illusions.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
extends: repetition
|
||||||
|
message: "'%s' is repeated!"
|
||||||
|
level: warning
|
||||||
|
alpha: true
|
||||||
|
action:
|
||||||
|
name: edit
|
||||||
|
params:
|
||||||
|
- truncate
|
||||||
|
- " "
|
||||||
|
tokens:
|
||||||
|
- '[^\s]+'
|
||||||
183
docs/source/.vale/write-good/Passive.yml
Normal file
183
docs/source/.vale/write-good/Passive.yml
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
extends: existence
|
||||||
|
message: "'%s' may be passive voice. Use active voice if you can."
|
||||||
|
ignorecase: true
|
||||||
|
level: warning
|
||||||
|
raw:
|
||||||
|
- \b(am|are|were|being|is|been|was|be)\b\s*
|
||||||
|
tokens:
|
||||||
|
- '[\w]+ed'
|
||||||
|
- awoken
|
||||||
|
- beat
|
||||||
|
- become
|
||||||
|
- been
|
||||||
|
- begun
|
||||||
|
- bent
|
||||||
|
- beset
|
||||||
|
- bet
|
||||||
|
- bid
|
||||||
|
- bidden
|
||||||
|
- bitten
|
||||||
|
- bled
|
||||||
|
- blown
|
||||||
|
- born
|
||||||
|
- bought
|
||||||
|
- bound
|
||||||
|
- bred
|
||||||
|
- broadcast
|
||||||
|
- broken
|
||||||
|
- brought
|
||||||
|
- built
|
||||||
|
- burnt
|
||||||
|
- burst
|
||||||
|
- cast
|
||||||
|
- caught
|
||||||
|
- chosen
|
||||||
|
- clung
|
||||||
|
- come
|
||||||
|
- cost
|
||||||
|
- crept
|
||||||
|
- cut
|
||||||
|
- dealt
|
||||||
|
- dived
|
||||||
|
- done
|
||||||
|
- drawn
|
||||||
|
- dreamt
|
||||||
|
- driven
|
||||||
|
- drunk
|
||||||
|
- dug
|
||||||
|
- eaten
|
||||||
|
- fallen
|
||||||
|
- fed
|
||||||
|
- felt
|
||||||
|
- fit
|
||||||
|
- fled
|
||||||
|
- flown
|
||||||
|
- flung
|
||||||
|
- forbidden
|
||||||
|
- foregone
|
||||||
|
- forgiven
|
||||||
|
- forgotten
|
||||||
|
- forsaken
|
||||||
|
- fought
|
||||||
|
- found
|
||||||
|
- frozen
|
||||||
|
- given
|
||||||
|
- gone
|
||||||
|
- gotten
|
||||||
|
- ground
|
||||||
|
- grown
|
||||||
|
- heard
|
||||||
|
- held
|
||||||
|
- hidden
|
||||||
|
- hit
|
||||||
|
- hung
|
||||||
|
- hurt
|
||||||
|
- kept
|
||||||
|
- knelt
|
||||||
|
- knit
|
||||||
|
- known
|
||||||
|
- laid
|
||||||
|
- lain
|
||||||
|
- leapt
|
||||||
|
- learnt
|
||||||
|
- led
|
||||||
|
- left
|
||||||
|
- lent
|
||||||
|
- let
|
||||||
|
- lighted
|
||||||
|
- lost
|
||||||
|
- made
|
||||||
|
- meant
|
||||||
|
- met
|
||||||
|
- misspelt
|
||||||
|
- mistaken
|
||||||
|
- mown
|
||||||
|
- overcome
|
||||||
|
- overdone
|
||||||
|
- overtaken
|
||||||
|
- overthrown
|
||||||
|
- paid
|
||||||
|
- pled
|
||||||
|
- proven
|
||||||
|
- put
|
||||||
|
- quit
|
||||||
|
- read
|
||||||
|
- rid
|
||||||
|
- ridden
|
||||||
|
- risen
|
||||||
|
- run
|
||||||
|
- rung
|
||||||
|
- said
|
||||||
|
- sat
|
||||||
|
- sawn
|
||||||
|
- seen
|
||||||
|
- sent
|
||||||
|
- set
|
||||||
|
- sewn
|
||||||
|
- shaken
|
||||||
|
- shaven
|
||||||
|
- shed
|
||||||
|
- shod
|
||||||
|
- shone
|
||||||
|
- shorn
|
||||||
|
- shot
|
||||||
|
- shown
|
||||||
|
- shrunk
|
||||||
|
- shut
|
||||||
|
- slain
|
||||||
|
- slept
|
||||||
|
- slid
|
||||||
|
- slit
|
||||||
|
- slung
|
||||||
|
- smitten
|
||||||
|
- sold
|
||||||
|
- sought
|
||||||
|
- sown
|
||||||
|
- sped
|
||||||
|
- spent
|
||||||
|
- spilt
|
||||||
|
- spit
|
||||||
|
- split
|
||||||
|
- spoken
|
||||||
|
- spread
|
||||||
|
- sprung
|
||||||
|
- spun
|
||||||
|
- stolen
|
||||||
|
- stood
|
||||||
|
- stridden
|
||||||
|
- striven
|
||||||
|
- struck
|
||||||
|
- strung
|
||||||
|
- stuck
|
||||||
|
- stung
|
||||||
|
- stunk
|
||||||
|
- sung
|
||||||
|
- sunk
|
||||||
|
- swept
|
||||||
|
- swollen
|
||||||
|
- sworn
|
||||||
|
- swum
|
||||||
|
- swung
|
||||||
|
- taken
|
||||||
|
- taught
|
||||||
|
- thought
|
||||||
|
- thrived
|
||||||
|
- thrown
|
||||||
|
- thrust
|
||||||
|
- told
|
||||||
|
- torn
|
||||||
|
- trodden
|
||||||
|
- understood
|
||||||
|
- upheld
|
||||||
|
- upset
|
||||||
|
- wed
|
||||||
|
- wept
|
||||||
|
- withheld
|
||||||
|
- withstood
|
||||||
|
- woken
|
||||||
|
- won
|
||||||
|
- worn
|
||||||
|
- wound
|
||||||
|
- woven
|
||||||
|
- written
|
||||||
|
- wrung
|
||||||
27
docs/source/.vale/write-good/README.md
Normal file
27
docs/source/.vale/write-good/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Based on [write-good](https://github.com/btford/write-good).
|
||||||
|
|
||||||
|
> Naive linter for English prose for developers who can't write good and wanna learn to do other stuff good too.
|
||||||
|
|
||||||
|
```
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Brian Ford
|
||||||
|
|
||||||
|
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.
|
||||||
|
```
|
||||||
5
docs/source/.vale/write-good/So.yml
Normal file
5
docs/source/.vale/write-good/So.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
extends: existence
|
||||||
|
message: "Don't start a sentence with '%s'."
|
||||||
|
level: error
|
||||||
|
raw:
|
||||||
|
- '(?:[;-]\s)so[\s,]|\bSo[\s,]'
|
||||||
6
docs/source/.vale/write-good/ThereIs.yml
Normal file
6
docs/source/.vale/write-good/ThereIs.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
extends: existence
|
||||||
|
message: "Don't start a sentence with '%s'."
|
||||||
|
ignorecase: false
|
||||||
|
level: error
|
||||||
|
raw:
|
||||||
|
- '(?:[;-]\s)There\s(is|are)|\bThere\s(is|are)\b'
|
||||||
221
docs/source/.vale/write-good/TooWordy.yml
Normal file
221
docs/source/.vale/write-good/TooWordy.yml
Normal file
|
|
@ -0,0 +1,221 @@
|
||||||
|
extends: existence
|
||||||
|
message: "'%s' is too wordy."
|
||||||
|
ignorecase: true
|
||||||
|
level: warning
|
||||||
|
tokens:
|
||||||
|
- a number of
|
||||||
|
- abundance
|
||||||
|
- accede to
|
||||||
|
- accelerate
|
||||||
|
- accentuate
|
||||||
|
- accompany
|
||||||
|
- accomplish
|
||||||
|
- accorded
|
||||||
|
- accrue
|
||||||
|
- acquiesce
|
||||||
|
- acquire
|
||||||
|
- additional
|
||||||
|
- adjacent to
|
||||||
|
- adjustment
|
||||||
|
- admissible
|
||||||
|
- advantageous
|
||||||
|
- adversely impact
|
||||||
|
- advise
|
||||||
|
- aforementioned
|
||||||
|
- aggregate
|
||||||
|
- aircraft
|
||||||
|
- all of
|
||||||
|
- all things considered
|
||||||
|
- alleviate
|
||||||
|
- allocate
|
||||||
|
- along the lines of
|
||||||
|
- already existing
|
||||||
|
- alternatively
|
||||||
|
- amazing
|
||||||
|
- ameliorate
|
||||||
|
- anticipate
|
||||||
|
- apparent
|
||||||
|
- appreciable
|
||||||
|
- as a matter of fact
|
||||||
|
- as a means of
|
||||||
|
- as far as I'm concerned
|
||||||
|
- as of yet
|
||||||
|
- as to
|
||||||
|
- as yet
|
||||||
|
- ascertain
|
||||||
|
- assistance
|
||||||
|
- at the present time
|
||||||
|
- at this time
|
||||||
|
- attain
|
||||||
|
- attributable to
|
||||||
|
- authorize
|
||||||
|
- because of the fact that
|
||||||
|
- belated
|
||||||
|
- benefit from
|
||||||
|
- bestow
|
||||||
|
- by means of
|
||||||
|
- by virtue of
|
||||||
|
- by virtue of the fact that
|
||||||
|
- cease
|
||||||
|
- close proximity
|
||||||
|
- commence
|
||||||
|
- comply with
|
||||||
|
- concerning
|
||||||
|
- consequently
|
||||||
|
- consolidate
|
||||||
|
- constitutes
|
||||||
|
- demonstrate
|
||||||
|
- depart
|
||||||
|
- designate
|
||||||
|
- discontinue
|
||||||
|
- due to the fact that
|
||||||
|
- each and every
|
||||||
|
- economical
|
||||||
|
- eliminate
|
||||||
|
- elucidate
|
||||||
|
- employ
|
||||||
|
- endeavor
|
||||||
|
- enumerate
|
||||||
|
- equitable
|
||||||
|
- equivalent
|
||||||
|
- evaluate
|
||||||
|
- evidenced
|
||||||
|
- exclusively
|
||||||
|
- expedite
|
||||||
|
- expend
|
||||||
|
- expiration
|
||||||
|
- facilitate
|
||||||
|
- factual evidence
|
||||||
|
- feasible
|
||||||
|
- finalize
|
||||||
|
- first and foremost
|
||||||
|
- for all intents and purposes
|
||||||
|
- for the most part
|
||||||
|
- for the purpose of
|
||||||
|
- forfeit
|
||||||
|
- formulate
|
||||||
|
- have a tendency to
|
||||||
|
- honest truth
|
||||||
|
- however
|
||||||
|
- if and when
|
||||||
|
- impacted
|
||||||
|
- implement
|
||||||
|
- in a manner of speaking
|
||||||
|
- in a timely manner
|
||||||
|
- in a very real sense
|
||||||
|
- in accordance with
|
||||||
|
- in addition
|
||||||
|
- in all likelihood
|
||||||
|
- in an effort to
|
||||||
|
- in between
|
||||||
|
- in excess of
|
||||||
|
- in lieu of
|
||||||
|
- in light of the fact that
|
||||||
|
- in many cases
|
||||||
|
- in my opinion
|
||||||
|
- in order to
|
||||||
|
- in regard to
|
||||||
|
- in some instances
|
||||||
|
- in terms of
|
||||||
|
- in the case of
|
||||||
|
- in the event that
|
||||||
|
- in the final analysis
|
||||||
|
- in the nature of
|
||||||
|
- in the near future
|
||||||
|
- in the process of
|
||||||
|
- inception
|
||||||
|
- incumbent upon
|
||||||
|
- indicate
|
||||||
|
- indication
|
||||||
|
- initiate
|
||||||
|
- irregardless
|
||||||
|
- is applicable to
|
||||||
|
- is authorized to
|
||||||
|
- is responsible for
|
||||||
|
- it is
|
||||||
|
- it is essential
|
||||||
|
- it seems that
|
||||||
|
- it was
|
||||||
|
- magnitude
|
||||||
|
- maximum
|
||||||
|
- methodology
|
||||||
|
- minimize
|
||||||
|
- minimum
|
||||||
|
- modify
|
||||||
|
- monitor
|
||||||
|
- multiple
|
||||||
|
- necessitate
|
||||||
|
- nevertheless
|
||||||
|
- not certain
|
||||||
|
- not many
|
||||||
|
- not often
|
||||||
|
- not unless
|
||||||
|
- not unlike
|
||||||
|
- notwithstanding
|
||||||
|
- null and void
|
||||||
|
- numerous
|
||||||
|
- objective
|
||||||
|
- obligate
|
||||||
|
- obtain
|
||||||
|
- on the contrary
|
||||||
|
- on the other hand
|
||||||
|
- one particular
|
||||||
|
- optimum
|
||||||
|
- overall
|
||||||
|
- owing to the fact that
|
||||||
|
- participate
|
||||||
|
- particulars
|
||||||
|
- pass away
|
||||||
|
- pertaining to
|
||||||
|
- point in time
|
||||||
|
- portion
|
||||||
|
- possess
|
||||||
|
- preclude
|
||||||
|
- previously
|
||||||
|
- prior to
|
||||||
|
- prioritize
|
||||||
|
- procure
|
||||||
|
- proficiency
|
||||||
|
- provided that
|
||||||
|
- purchase
|
||||||
|
- put simply
|
||||||
|
- readily apparent
|
||||||
|
- refer back
|
||||||
|
- regarding
|
||||||
|
- relocate
|
||||||
|
- remainder
|
||||||
|
- remuneration
|
||||||
|
- requirement
|
||||||
|
- reside
|
||||||
|
- residence
|
||||||
|
- retain
|
||||||
|
- satisfy
|
||||||
|
- shall
|
||||||
|
- should you wish
|
||||||
|
- similar to
|
||||||
|
- solicit
|
||||||
|
- span across
|
||||||
|
- strategize
|
||||||
|
- subsequent
|
||||||
|
- substantial
|
||||||
|
- successfully complete
|
||||||
|
- sufficient
|
||||||
|
- terminate
|
||||||
|
- the month of
|
||||||
|
- the point I am trying to make
|
||||||
|
- therefore
|
||||||
|
- time period
|
||||||
|
- took advantage of
|
||||||
|
- transmit
|
||||||
|
- transpire
|
||||||
|
- type of
|
||||||
|
- until such time as
|
||||||
|
- utilization
|
||||||
|
- utilize
|
||||||
|
- validate
|
||||||
|
- various different
|
||||||
|
- what I mean to say is
|
||||||
|
- whether or not
|
||||||
|
- with respect to
|
||||||
|
- with the exception of
|
||||||
|
- witnessed
|
||||||
207
docs/source/.vale/write-good/Weasel.yml
Normal file
207
docs/source/.vale/write-good/Weasel.yml
Normal file
|
|
@ -0,0 +1,207 @@
|
||||||
|
extends: existence
|
||||||
|
message: "'%s' is a weasel word!"
|
||||||
|
ignorecase: true
|
||||||
|
level: warning
|
||||||
|
tokens:
|
||||||
|
- absolutely
|
||||||
|
- accidentally
|
||||||
|
- additionally
|
||||||
|
- allegedly
|
||||||
|
- alternatively
|
||||||
|
- angrily
|
||||||
|
- anxiously
|
||||||
|
- approximately
|
||||||
|
- awkwardly
|
||||||
|
- badly
|
||||||
|
- barely
|
||||||
|
- beautifully
|
||||||
|
- blindly
|
||||||
|
- boldly
|
||||||
|
- bravely
|
||||||
|
- brightly
|
||||||
|
- briskly
|
||||||
|
- bristly
|
||||||
|
- bubbly
|
||||||
|
- busily
|
||||||
|
- calmly
|
||||||
|
- carefully
|
||||||
|
- carelessly
|
||||||
|
- cautiously
|
||||||
|
- cheerfully
|
||||||
|
- clearly
|
||||||
|
- closely
|
||||||
|
- coldly
|
||||||
|
- completely
|
||||||
|
- consequently
|
||||||
|
- correctly
|
||||||
|
- courageously
|
||||||
|
- crinkly
|
||||||
|
- cruelly
|
||||||
|
- crumbly
|
||||||
|
- cuddly
|
||||||
|
- currently
|
||||||
|
- daily
|
||||||
|
- daringly
|
||||||
|
- deadly
|
||||||
|
- definitely
|
||||||
|
- deliberately
|
||||||
|
- doubtfully
|
||||||
|
- dumbly
|
||||||
|
- eagerly
|
||||||
|
- early
|
||||||
|
- easily
|
||||||
|
- elegantly
|
||||||
|
- enormously
|
||||||
|
- enthusiastically
|
||||||
|
- equally
|
||||||
|
- especially
|
||||||
|
- eventually
|
||||||
|
- exactly
|
||||||
|
- exceedingly
|
||||||
|
- exclusively
|
||||||
|
- extremely
|
||||||
|
- fairly
|
||||||
|
- faithfully
|
||||||
|
- fatally
|
||||||
|
- fiercely
|
||||||
|
- finally
|
||||||
|
- fondly
|
||||||
|
- few
|
||||||
|
- foolishly
|
||||||
|
- fortunately
|
||||||
|
- frankly
|
||||||
|
- frantically
|
||||||
|
- generously
|
||||||
|
- gently
|
||||||
|
- giggly
|
||||||
|
- gladly
|
||||||
|
- gracefully
|
||||||
|
- greedily
|
||||||
|
- happily
|
||||||
|
- hardly
|
||||||
|
- hastily
|
||||||
|
- healthily
|
||||||
|
- heartily
|
||||||
|
- helpfully
|
||||||
|
- honestly
|
||||||
|
- hourly
|
||||||
|
- hungrily
|
||||||
|
- hurriedly
|
||||||
|
- immediately
|
||||||
|
- impatiently
|
||||||
|
- inadequately
|
||||||
|
- ingeniously
|
||||||
|
- innocently
|
||||||
|
- inquisitively
|
||||||
|
- interestingly
|
||||||
|
- irritably
|
||||||
|
- jiggly
|
||||||
|
- joyously
|
||||||
|
- justly
|
||||||
|
- kindly
|
||||||
|
- largely
|
||||||
|
- lately
|
||||||
|
- lazily
|
||||||
|
- likely
|
||||||
|
- literally
|
||||||
|
- lonely
|
||||||
|
- loosely
|
||||||
|
- loudly
|
||||||
|
- loudly
|
||||||
|
- luckily
|
||||||
|
- madly
|
||||||
|
- many
|
||||||
|
- mentally
|
||||||
|
- mildly
|
||||||
|
- monthly
|
||||||
|
- mortally
|
||||||
|
- mostly
|
||||||
|
- mysteriously
|
||||||
|
- neatly
|
||||||
|
- nervously
|
||||||
|
- nightly
|
||||||
|
- noisily
|
||||||
|
- normally
|
||||||
|
- obediently
|
||||||
|
- occasionally
|
||||||
|
- only
|
||||||
|
- openly
|
||||||
|
- painfully
|
||||||
|
- particularly
|
||||||
|
- patiently
|
||||||
|
- perfectly
|
||||||
|
- politely
|
||||||
|
- poorly
|
||||||
|
- powerfully
|
||||||
|
- presumably
|
||||||
|
- previously
|
||||||
|
- promptly
|
||||||
|
- punctually
|
||||||
|
- quarterly
|
||||||
|
- quickly
|
||||||
|
- quietly
|
||||||
|
- rapidly
|
||||||
|
- rarely
|
||||||
|
- really
|
||||||
|
- recently
|
||||||
|
- recklessly
|
||||||
|
- regularly
|
||||||
|
- remarkably
|
||||||
|
- relatively
|
||||||
|
- reluctantly
|
||||||
|
- repeatedly
|
||||||
|
- rightfully
|
||||||
|
- roughly
|
||||||
|
- rudely
|
||||||
|
- sadly
|
||||||
|
- safely
|
||||||
|
- selfishly
|
||||||
|
- sensibly
|
||||||
|
- seriously
|
||||||
|
- sharply
|
||||||
|
- shortly
|
||||||
|
- shyly
|
||||||
|
- significantly
|
||||||
|
- silently
|
||||||
|
- simply
|
||||||
|
- sleepily
|
||||||
|
- slowly
|
||||||
|
- smartly
|
||||||
|
- smelly
|
||||||
|
- smoothly
|
||||||
|
- softly
|
||||||
|
- solemnly
|
||||||
|
- sparkly
|
||||||
|
- speedily
|
||||||
|
- stealthily
|
||||||
|
- sternly
|
||||||
|
- stupidly
|
||||||
|
- substantially
|
||||||
|
- successfully
|
||||||
|
- suddenly
|
||||||
|
- surprisingly
|
||||||
|
- suspiciously
|
||||||
|
- swiftly
|
||||||
|
- tenderly
|
||||||
|
- tensely
|
||||||
|
- thoughtfully
|
||||||
|
- tightly
|
||||||
|
- timely
|
||||||
|
- truthfully
|
||||||
|
- unexpectedly
|
||||||
|
- unfortunately
|
||||||
|
- usually
|
||||||
|
- very
|
||||||
|
- victoriously
|
||||||
|
- violently
|
||||||
|
- vivaciously
|
||||||
|
- warmly
|
||||||
|
- waverly
|
||||||
|
- weakly
|
||||||
|
- wearily
|
||||||
|
- weekly
|
||||||
|
- wildly
|
||||||
|
- wisely
|
||||||
|
- worldly
|
||||||
|
- wrinkly
|
||||||
|
- yearly
|
||||||
4
docs/source/.vale/write-good/meta.json
Normal file
4
docs/source/.vale/write-good/meta.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"feed": "https://github.com/errata-ai/write-good/releases.atom",
|
||||||
|
"vale_version": ">=1.0.0"
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
```{code-block}
|
```{code-block}
|
||||||
:caption: In-game
|
:caption: In-game
|
||||||
> set obj/myattr = "test"
|
> set obj/myattr = "test"
|
||||||
```
|
```
|
||||||
```{code-block} python
|
```{code-block} python
|
||||||
:caption: In-code, using the .db wrapper
|
:caption: In-code, using the .db wrapper
|
||||||
obj.db.foo = [1, 2, 3, "bar"]
|
obj.db.foo = [1, 2, 3, "bar"]
|
||||||
|
|
@ -16,8 +16,8 @@ value = attributes.get("myattr", category="bar")
|
||||||
```
|
```
|
||||||
```{code-block} python
|
```{code-block} python
|
||||||
:caption: In-code, using `AttributeProperty` at class level
|
:caption: In-code, using `AttributeProperty` at class level
|
||||||
from evennia import DefaultObject
|
from evennia import DefaultObject
|
||||||
from evennia import AttributeProperty
|
from evennia import AttributeProperty
|
||||||
|
|
||||||
class MyObject(DefaultObject):
|
class MyObject(DefaultObject):
|
||||||
foo = AttributeProperty(default=[1, 2, 3, "bar"])
|
foo = AttributeProperty(default=[1, 2, 3, "bar"])
|
||||||
|
|
@ -25,20 +25,20 @@ class MyObject(DefaultObject):
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any
|
_Attributes_ allow you to to store arbitrary data on objects and make sure the data survives a server reboot. An Attribute can store pretty much any
|
||||||
Python data structure and data type, like numbers, strings, lists, dicts etc. You can also
|
Python data structure and data type, like numbers, strings, lists, dicts etc. You can also
|
||||||
store (references to) database objects like characters and rooms.
|
store (references to) database objects like characters and rooms.
|
||||||
|
|
||||||
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything
|
- [What can be stored in an Attribute](#what-types-of-data-can-i-save-in-an-attribute) is a must-read to avoid being surprised, also for experienced developers. Attributes can store _almost_ everything
|
||||||
but you need to know the quirks.
|
but you need to know the quirks.
|
||||||
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
|
- [NAttributes](#in-memory-attributes-nattributes) are the in-memory, non-persistent
|
||||||
siblings of Attributes.
|
siblings of Attributes.
|
||||||
- [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands.
|
- [Managing Attributes In-game](#managing-attributes-in-game) for in-game builder commands.
|
||||||
|
|
||||||
## Managing Attributes in Code
|
## Managing Attributes in Code
|
||||||
|
|
||||||
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
|
Attributes are usually handled in code. All [Typeclassed](./Typeclasses.md) entities
|
||||||
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
|
([Accounts](./Accounts.md), [Objects](./Objects.md), [Scripts](./Scripts.md) and
|
||||||
[Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
|
[Channels](./Channels.md)) can (and usually do) have Attributes associated with them. There
|
||||||
are three ways to manage Attributes, all of which can be mixed.
|
are three ways to manage Attributes, all of which can be mixed.
|
||||||
|
|
||||||
|
|
@ -50,8 +50,8 @@ are three ways to manage Attributes, all of which can be mixed.
|
||||||
|
|
||||||
The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`)
|
The simplest way to get/set Attributes is to use the `.db` shortcut. This allows for setting and getting Attributes that lack a _category_ (having category `None`)
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import evennia
|
import evennia
|
||||||
|
|
||||||
obj = evennia.create_object(key="Foo")
|
obj = evennia.create_object(key="Foo")
|
||||||
|
|
||||||
|
|
@ -64,10 +64,10 @@ obj.db.self_reference = obj # stores a reference to the obj
|
||||||
rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element
|
rose = evennia.search_object(key="rose")[0] # returns a list, grab 0th element
|
||||||
rose.db.has_thorns = True
|
rose.db.has_thorns = True
|
||||||
|
|
||||||
# retrieving
|
# retrieving
|
||||||
val1 = obj.db.foo1
|
val1 = obj.db.foo1
|
||||||
val2 = obj.db.foo2
|
val2 = obj.db.foo2
|
||||||
weap = obj.db.weapon
|
weap = obj.db.weapon
|
||||||
myself = obj.db.self_reference # retrieve reference from db, get object back
|
myself = obj.db.self_reference # retrieve reference from db, get object back
|
||||||
|
|
||||||
is_ouch = rose.db.has_thorns
|
is_ouch = rose.db.has_thorns
|
||||||
|
|
@ -75,25 +75,25 @@ is_ouch = rose.db.has_thorns
|
||||||
# this will return None, not AttributeError!
|
# this will return None, not AttributeError!
|
||||||
not_found = obj.db.jiwjpowiwwerw
|
not_found = obj.db.jiwjpowiwwerw
|
||||||
|
|
||||||
# returns all Attributes on the object
|
# returns all Attributes on the object
|
||||||
obj.db.all
|
obj.db.all
|
||||||
|
|
||||||
# delete an Attribute
|
# delete an Attribute
|
||||||
del obj.db.foo2
|
del obj.db.foo2
|
||||||
```
|
```
|
||||||
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
|
Trying to access a non-existing Attribute will never lead to an `AttributeError`. Instead
|
||||||
you will get `None` back. The special `.db.all` will return a list of all Attributes on
|
you will get `None` back. The special `.db.all` will return a list of all Attributes on
|
||||||
the object. You can replace this with your own Attribute `all` if you want, it will replace the
|
the object. You can replace this with your own Attribute `all` if you want, it will replace the
|
||||||
default `all` functionality until you delete it again.
|
default `all` functionality until you delete it again.
|
||||||
|
|
||||||
### Using .attributes
|
### Using .attributes
|
||||||
|
|
||||||
If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of
|
If you want to group your Attribute in a category, or don't know the name of the Attribute beforehand, you can make use of
|
||||||
the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
|
the [AttributeHandler](evennia.typeclasses.attributes.AttributeHandler), available as `.attributes` on all typeclassed entities. With no extra keywords, this is identical to using the `.db` shortcut (`.db` is actually using the `AttributeHandler` internally):
|
||||||
|
|
||||||
|
```python
|
||||||
|
is_ouch = rose.attributes.get("has_thorns")
|
||||||
|
|
||||||
```python
|
|
||||||
is_ouch = rose.attributes.get("has_thorns")
|
|
||||||
|
|
||||||
obj.attributes.add("helmet", "Knight's helmet")
|
obj.attributes.add("helmet", "Knight's helmet")
|
||||||
helmet = obj.attributes.get("helmet")
|
helmet = obj.attributes.get("helmet")
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ obj.attributes.add("my game log", "long text about ...")
|
||||||
|
|
||||||
By using a category you can separate same-named Attributes on the same object to help organization.
|
By using a category you can separate same-named Attributes on the same object to help organization.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# store (let's say we have gold_necklace and ringmail_armor from before)
|
# store (let's say we have gold_necklace and ringmail_armor from before)
|
||||||
obj.attributes.add("neck", gold_necklace, category="clothing")
|
obj.attributes.add("neck", gold_necklace, category="clothing")
|
||||||
obj.attributes.add("neck", ringmail_armor, category="armor")
|
obj.attributes.add("neck", ringmail_armor, category="armor")
|
||||||
|
|
@ -113,19 +113,19 @@ neck_clothing = obj.attributes.get("neck", category="clothing")
|
||||||
neck_armor = obj.attributes.get("neck", category="armor")
|
neck_armor = obj.attributes.get("neck", category="armor")
|
||||||
```
|
```
|
||||||
|
|
||||||
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
|
If you don't specify a category, the Attribute's `category` will be `None` and can thus also be found via `.db`. `None` is considered a category of its own, so you won't find `None`-category Attributes mixed with Attributes having categories.
|
||||||
|
|
||||||
Here are the methods of the `AttributeHandler`. See
|
Here are the methods of the `AttributeHandler`. See
|
||||||
the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
|
the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for more details.
|
||||||
|
|
||||||
- `has(...)` - this checks if the object has an Attribute with this key. This is equivalent
|
- `has(...)` - this checks if the object has an Attribute with this key. This is equivalent
|
||||||
to doing `obj.db.attrname` except you can also check for a specific `category.
|
to doing `obj.db.attrname` except you can also check for a specific `category.
|
||||||
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
|
- `get(...)` - this retrieves the given Attribute. You can also provide a `default` value to return
|
||||||
if the Attribute is not defined (instead of None). By supplying an
|
if the Attribute is not defined (instead of None). By supplying an
|
||||||
`accessing_object` to the call one can also make sure to check permissions before modifying
|
`accessing_object` to the call one can also make sure to check permissions before modifying
|
||||||
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
|
anything. The `raise_exception` kwarg allows you to raise an `AttributeError` instead of returning
|
||||||
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
|
`None` when you access a non-existing `Attribute`. The `strattr` kwarg tells the system to store
|
||||||
the Attribute as a raw string rather than to pickle it. While an optimization this should usually
|
the Attribute as a raw string rather than to pickle it. While an optimization this should usually
|
||||||
not be used unless the Attribute is used for some particular, limited purpose.
|
not be used unless the Attribute is used for some particular, limited purpose.
|
||||||
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
|
- `add(...)` - this adds a new Attribute to the object. An optional [lockstring](./Locks.md) can be
|
||||||
supplied here to restrict future access and also the call itself may be checked against locks.
|
supplied here to restrict future access and also the call itself may be checked against locks.
|
||||||
|
|
@ -135,30 +135,30 @@ the [AttributeHandler API](evennia.typeclasses.attributes.AttributeHandler) for
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
try:
|
try:
|
||||||
# raise error if Attribute foo does not exist
|
# raise error if Attribute foo does not exist
|
||||||
val = obj.attributes.get("foo", raise_exception=True):
|
val = obj.attributes.get("foo", raise_exception=True):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# ...
|
# ...
|
||||||
|
|
||||||
# return default value if foo2 doesn't exist
|
# return default value if foo2 doesn't exist
|
||||||
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
|
val2 = obj.attributes.get("foo2", default=[1, 2, 3, "bar"])
|
||||||
|
|
||||||
# delete foo if it exists (will silently fail if unset, unless
|
# delete foo if it exists (will silently fail if unset, unless
|
||||||
# raise_exception is set)
|
# raise_exception is set)
|
||||||
obj.attributes.remove("foo")
|
obj.attributes.remove("foo")
|
||||||
|
|
||||||
# view all clothes on obj
|
# view all clothes on obj
|
||||||
all_clothes = obj.attributes.all(category="clothes")
|
all_clothes = obj.attributes.all(category="clothes")
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using AttributeProperty
|
### Using AttributeProperty
|
||||||
|
|
||||||
The third way to set up an Attribute is to use an `AttributeProperty`. This
|
The third way to set up an Attribute is to use an `AttributeProperty`. This
|
||||||
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code.
|
is done on the _class level_ of your typeclass and allows you to treat Attributes a bit like Django database Fields. Unlike using `.db` and `.attributes`, an `AttributeProperty` can't be created on the fly, you must assign it in the class code.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# mygame/typeclasses/characters.py
|
# mygame/typeclasses/characters.py
|
||||||
|
|
||||||
from evennia import DefaultCharacter
|
from evennia import DefaultCharacter
|
||||||
|
|
@ -173,16 +173,16 @@ class Character(DefaultCharacter):
|
||||||
|
|
||||||
sleepy = AttributeProperty(False, autocreate=False)
|
sleepy = AttributeProperty(False, autocreate=False)
|
||||||
poisoned = AttributeProperty(False, autocreate=False)
|
poisoned = AttributeProperty(False, autocreate=False)
|
||||||
|
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
When a new instance of the class is created, new `Attributes` will be created with the value and category given.
|
When a new instance of the class is created, new `Attributes` will be created with the value and category given.
|
||||||
|
|
||||||
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
|
With `AttributeProperty`'s set up like this, one can access the underlying `Attribute` like a regular property on the created object:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
char = create_object(Character)
|
char = create_object(Character)
|
||||||
|
|
||||||
char.strength # returns 10
|
char.strength # returns 10
|
||||||
|
|
@ -195,15 +195,15 @@ char.db.sleepy # returns None because autocreate=False (see below)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```{warning}
|
```{warning}
|
||||||
Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors.
|
Be careful to not assign AttributeProperty's to names of properties and methods already existing on the class, like 'key' or 'at_object_creation'. That could lead to very confusing errors.
|
||||||
```
|
```
|
||||||
|
|
||||||
The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set.
|
The `autocreate=False` (default is `True`) used for `sleepy` and `poisoned` is worth a closer explanation. When `False`, _no_ Attribute will be auto-created for these AttributProperties unless they are _explicitly_ set.
|
||||||
The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default.
|
The advantage of not creating an Attribute is that the default value given to `AttributeProperty` is returned with no database access unless you change it. This also means that if you want to change the default later, all entities previously create will inherit the new default.
|
||||||
The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database):
|
The drawback is that without a database precense you can't find the Attribute via `.db` and `.attributes.get` (or by querying for it in other ways in the database):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
char.sleepy # returns False, no db access
|
char.sleepy # returns False, no db access
|
||||||
|
|
||||||
char.db.sleepy # returns None - no Attribute exists
|
char.db.sleepy # returns None - no Attribute exists
|
||||||
|
|
@ -217,39 +217,39 @@ char.sleepy # now returns True, involves db access
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can e.g. `del char.strength` to set the value back to the default (the value defined
|
You can e.g. `del char.strength` to set the value back to the default (the value defined
|
||||||
in the `AttributeProperty`).
|
in the `AttributeProperty`).
|
||||||
|
|
||||||
See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
|
See the [AttributeProperty API](evennia.typeclasses.attributes.AttributeProperty) for more details on how to create it with special options, like giving access-restrictions.
|
||||||
|
|
||||||
|
|
||||||
## Managing Attributes in-game
|
## Managing Attributes in-game
|
||||||
|
|
||||||
Attributes are mainly used by code. But one can also allow the builder to use Attributes to
|
Attributes are mainly used by code. But one can also allow the builder to use Attributes to
|
||||||
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
|
'turn knobs' in-game. For example a builder could want to manually tweak the "level" Attribute of an
|
||||||
enemy NPC to lower its difficuly.
|
enemy NPC to lower its difficuly.
|
||||||
|
|
||||||
When setting Attributes this way, you are severely limited in what can be stored - this is because
|
When setting Attributes this way, you are severely limited in what can be stored - this is because
|
||||||
giving players (even builders) the ability to store arbitrary Python would be a severe security
|
giving players (even builders) the ability to store arbitrary Python would be a severe security
|
||||||
problem.
|
problem.
|
||||||
|
|
||||||
In game you can set an Attribute like this:
|
In game you can set an Attribute like this:
|
||||||
|
|
||||||
set myobj/foo = "bar"
|
set myobj/foo = "bar"
|
||||||
|
|
||||||
To view, do
|
To view, do
|
||||||
|
|
||||||
set myobj/foo
|
set myobj/foo
|
||||||
|
|
||||||
or see them together with all object-info with
|
or see them together with all object-info with
|
||||||
|
|
||||||
examine myobj
|
examine myobj
|
||||||
|
|
||||||
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
|
The first `set`-example will store a new Attribute `foo` on the object `myobj` and give it the
|
||||||
value "bar".
|
value "bar".
|
||||||
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
|
You can store numbers, booleans, strings, tuples, lists and dicts this way. But if
|
||||||
you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings
|
you store a list/tuple/dict they must be proper Python structures and may _only_ contain strings
|
||||||
or numbers. If you try to insert an unsupported structure, the input will be converted to a
|
or numbers. If you try to insert an unsupported structure, the input will be converted to a
|
||||||
string.
|
string.
|
||||||
|
|
||||||
set myobj/mybool = True
|
set myobj/mybool = True
|
||||||
|
|
@ -263,8 +263,8 @@ For the last line you'll get a warning and the value instead will be saved as a
|
||||||
|
|
||||||
## Locking and checking Attributes
|
## Locking and checking Attributes
|
||||||
|
|
||||||
While the `set` command is limited to builders, individual Attributes are usually not
|
While the `set` command is limited to builders, individual Attributes are usually not
|
||||||
locked down. You may want to lock certain sensitive Attributes, in particular for games
|
locked down. You may want to lock certain sensitive Attributes, in particular for games
|
||||||
where you allow player building. You can add such limitations by adding a [lock string](./Locks.md)
|
where you allow player building. You can add such limitations by adding a [lock string](./Locks.md)
|
||||||
to your Attribute. A NAttribute have no locks.
|
to your Attribute. A NAttribute have no locks.
|
||||||
|
|
||||||
|
|
@ -273,7 +273,7 @@ The relevant lock types are
|
||||||
- `attrread` - limits who may read the value of the Attribute
|
- `attrread` - limits who may read the value of the Attribute
|
||||||
- `attredit` - limits who may set/change this Attribute
|
- `attredit` - limits who may set/change this Attribute
|
||||||
|
|
||||||
You must use the `AttributeHandler` to assign the lockstring to the Attribute:
|
You must use the `AttributeHandler` to assign the lockstring to the Attribute:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
lockstring = "attread:all();attredit:perm(Admins)"
|
lockstring = "attread:all();attredit:perm(Admins)"
|
||||||
|
|
@ -281,7 +281,7 @@ obj.attributes.add("myattr", "bar", lockstring=lockstring)"
|
||||||
```
|
```
|
||||||
|
|
||||||
If you already have an Attribute and want to add a lock in-place you can do so
|
If you already have an Attribute and want to add a lock in-place you can do so
|
||||||
by having the `AttributeHandler` return the `Attribute` object itself (rather than
|
by having the `AttributeHandler` return the `Attribute` object itself (rather than
|
||||||
its value) and then assign the lock to it directly:
|
its value) and then assign the lock to it directly:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -293,8 +293,8 @@ Note the `return_obj` keyword which makes sure to return the `Attribute` object
|
||||||
could be accessed.
|
could be accessed.
|
||||||
|
|
||||||
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
|
A lock is no good if nothing checks it -- and by default Evennia does not check locks on Attributes.
|
||||||
To check the `lockstring` you provided, make sure you include `accessing_obj` and set
|
To check the `lockstring` you provided, make sure you include `accessing_obj` and set
|
||||||
`default_access=False` as you make a `get` call.
|
`default_access=False` as you make a `get` call.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# in some command code where we want to limit
|
# in some command code where we want to limit
|
||||||
|
|
@ -328,13 +328,13 @@ values into a string representation before storing it to the database. This is d
|
||||||
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
|
With a single object, we mean anything that is *not iterable*, like numbers, strings or custom class
|
||||||
instances without the `__iter__` method.
|
instances without the `__iter__` method.
|
||||||
|
|
||||||
* You can generally store any non-iterable Python entity that can be pickled.
|
* You can generally store any non-iterable Python entity that can be _pickled_.
|
||||||
* Single database objects/typeclasses can be stored, despite them normally not being possible
|
* Single database objects/typeclasses can be stored, despite them normally not being possible
|
||||||
to pickle. Evennia wil convert them to an internal representation using their classname,
|
to pickle. Evennia will convert them to an internal representation using theihr classname,
|
||||||
database-id and creation-date with a microsecond precision. When retrieving, the object
|
database-id and creation-date with a microsecond precision. When retrieving, the object
|
||||||
instance will be re-fetched from the database using this information.
|
instance will be re-fetched from the database using this information.
|
||||||
* To convert the database object, Evennia must know it's there. If you *hide* a database object
|
* If you 'hide' a db-obj as a property on a custom class, Evennia will not be
|
||||||
inside a non-iterable class, you will run into errors - this is not supported!
|
able to find it to serialize it. For that you need to help it out (see below).
|
||||||
|
|
||||||
```{code-block} python
|
```{code-block} python
|
||||||
:caption: Valid assignments
|
:caption: Valid assignments
|
||||||
|
|
@ -345,16 +345,55 @@ obj.db.test1 = False
|
||||||
# a database object (will be stored as an internal representation)
|
# a database object (will be stored as an internal representation)
|
||||||
obj.db.test2 = myobj
|
obj.db.test2 = myobj
|
||||||
```
|
```
|
||||||
|
|
||||||
|
As mentioned, Evennia will not be able to automatically serialize db-objects
|
||||||
|
'hidden' in arbitrary properties on an object. This will lead to an error
|
||||||
|
when saving the Attribute.
|
||||||
|
|
||||||
```{code-block} python
|
```{code-block} python
|
||||||
:caption: Invalid, 'hidden' dbobject
|
:caption: Invalid, 'hidden' dbobject
|
||||||
|
# example of storing an invalid, "hidden" dbobject in Attribute
|
||||||
# example of an invalid, "hidden" dbobject
|
|
||||||
class Container:
|
class Container:
|
||||||
def __init__(self, mydbobj):
|
def __init__(self, mydbobj):
|
||||||
# no way for Evennia to know this is a database object!
|
# no way for Evennia to know this is a database object!
|
||||||
self.mydbobj = mydbobj
|
self.mydbobj = mydbobj
|
||||||
|
|
||||||
|
# let's assume myobj is a db-object
|
||||||
container = Container(myobj)
|
container = Container(myobj)
|
||||||
obj.db.invalid = container # will cause error!
|
obj.db.mydata = container # will raise error!
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
By adding two methods `__serialize_dbobjs__` and `__deserialize_dbobjs__` to the
|
||||||
|
object you want to save, you can pre-serialize and post-deserialize all 'hidden'
|
||||||
|
objects before Evennia's main serializer gets to work. Inside these methods, use Evennia's
|
||||||
|
[evennia.utils.dbserialize.dbserialize](api:evennia.utils.dbserialize.dbserialize) and
|
||||||
|
[dbunserialize](api:evennia.utils.dbserialize.dbunserialize) functions to safely
|
||||||
|
serialize the db-objects you want to store.
|
||||||
|
|
||||||
|
```{code-block} python
|
||||||
|
:caption: Fixing an invalid 'hidden' dbobj for storing in Attribute
|
||||||
|
|
||||||
|
from evennia.utils import dbserialize # important
|
||||||
|
|
||||||
|
class Container:
|
||||||
|
def __init__(self, mydbobj):
|
||||||
|
# A 'hidden' db-object
|
||||||
|
self.mydbobj = mydbobj
|
||||||
|
|
||||||
|
def __serialize_dbobjs__(self):
|
||||||
|
"""This is called before serialization and allows
|
||||||
|
us to custom-handle those 'hidden' dbobjs"""
|
||||||
|
self.mydbobj = dbserialize.dbserialize(self.mydbobj
|
||||||
|
|
||||||
|
def __deserialize_dbobjs__(self):
|
||||||
|
"""This is called after deserialization and allows you to
|
||||||
|
restore the 'hidden' dbobjs you serialized before"""
|
||||||
|
self.mydbobj = dbserialize.dbunserialize(self.mydbobj)
|
||||||
|
|
||||||
|
# let's assume myobj is a db-object
|
||||||
|
container = Container(myobj)
|
||||||
|
obj.db.mydata = container # will now work fine!
|
||||||
```
|
```
|
||||||
|
|
||||||
### Storing multiple objects
|
### Storing multiple objects
|
||||||
|
|
@ -404,6 +443,12 @@ obj.db.test8[2]["test"] = 5
|
||||||
# test8 is now [4,2,{"test":5}]
|
# test8 is now [4,2,{"test":5}]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that if make some advanced iterable object, and store an db-object on it in
|
||||||
|
a way such that it is _not_ returned by iterating over it, you have created a
|
||||||
|
'hidden' db-object. See [the previous section](#storing-single-objects) for how
|
||||||
|
to tell Evennia how to serialize such hidden objects safely.
|
||||||
|
|
||||||
|
|
||||||
### Retrieving Mutable objects
|
### Retrieving Mutable objects
|
||||||
|
|
||||||
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
|
A side effect of the way Evennia stores Attributes is that *mutable* iterables (iterables that can
|
||||||
|
|
@ -429,41 +474,41 @@ print(obj.db.mylist) # now also [1, 2, 3, 5]
|
||||||
```
|
```
|
||||||
|
|
||||||
When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_
|
When you extract your mutable Attribute data into a variable like `mylist`, think of it as getting a _snapshot_
|
||||||
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
|
of the variable. If you update the snapshot, it will save to the database, but this change _will not propagate to
|
||||||
any other snapshots you may have done previously_.
|
any other snapshots you may have done previously_.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
obj.db.mylist = [1, 2, 3, 4]
|
obj.db.mylist = [1, 2, 3, 4]
|
||||||
mylist1 = obj.db.mylist
|
mylist1 = obj.db.mylist
|
||||||
mylist2 = obj.db.mylist
|
mylist2 = obj.db.mylist
|
||||||
mylist1[3] = 5
|
mylist1[3] = 5
|
||||||
|
|
||||||
print(mylist1) # this is now [1, 2, 3, 5]
|
print(mylist1) # this is now [1, 2, 3, 5]
|
||||||
print(obj.db.mylist) # also updated to [1, 2, 3, 5]
|
print(obj.db.mylist) # also updated to [1, 2, 3, 5]
|
||||||
|
|
||||||
print(mylist2) # still [1, 2, 3, 4] !
|
print(mylist2) # still [1, 2, 3, 4] !
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```{sidebar}
|
```{sidebar}
|
||||||
Remember, the complexities of this section only relate to *mutable* iterables - things you can update
|
Remember, the complexities of this section only relate to *mutable* iterables - things you can update
|
||||||
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
|
in-place, like lists and dicts. [Immutable](https://en.wikipedia.org/wiki/Immutable) objects (strings,
|
||||||
numbers, tuples etc) are already disconnected from the database from the onset.
|
numbers, tuples etc) are already disconnected from the database from the onset.
|
||||||
```
|
```
|
||||||
|
|
||||||
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
|
To avoid confusion with mutable Attributes, only work with one variable (snapshot) at a time and save
|
||||||
back the results as needed.
|
back the results as needed.
|
||||||
|
|
||||||
You can also choose to "disconnect" the Attribute entirely from the
|
You can also choose to "disconnect" the Attribute entirely from the
|
||||||
database with the help of the `.deserialize()` method:
|
database with the help of the `.deserialize()` method:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
obj.db.mylist = [1, 2, 3, 4, {1: 2}]
|
obj.db.mylist = [1, 2, 3, 4, {1: 2}]
|
||||||
mylist = obj.db.mylist.deserialize()
|
mylist = obj.db.mylist.deserialize()
|
||||||
```
|
```
|
||||||
|
|
||||||
The result of this operation will be a structure only consisting of normal Python mutables (`list`
|
The result of this operation will be a structure only consisting of normal Python mutables (`list`
|
||||||
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
|
instead of `_SaverList`, `dict` instead of `_SaverDict` and so on). If you update it, you need to
|
||||||
explicitly save it back to the Attribute for it to save.
|
explicitly save it back to the Attribute for it to save.
|
||||||
|
|
||||||
## Properties of Attributes
|
## Properties of Attributes
|
||||||
|
|
@ -518,7 +563,7 @@ are **non-persistent** - they will _not_ survive a server reload.
|
||||||
Differences between `Attributes` and `NAttributes`:
|
Differences between `Attributes` and `NAttributes`:
|
||||||
|
|
||||||
- `NAttribute`s are always wiped on a server reload.
|
- `NAttribute`s are always wiped on a server reload.
|
||||||
- They only exist in memory and never involve the database at all, making them faster to
|
- They only exist in memory and never involve the database at all, making them faster to
|
||||||
access and edit than `Attribute`s.
|
access and edit than `Attribute`s.
|
||||||
- `NAttribute`s can store _any_ Python structure (and database object) without limit.
|
- `NAttribute`s can store _any_ Python structure (and database object) without limit.
|
||||||
- They can _not_ be set with the standard `set` command (but they are visible with `examine`)
|
- They can _not_ be set with the standard `set` command (but they are visible with `examine`)
|
||||||
|
|
@ -526,10 +571,10 @@ Differences between `Attributes` and `NAttributes`:
|
||||||
There are some important reasons we recommend using `ndb` to store temporary data rather than
|
There are some important reasons we recommend using `ndb` to store temporary data rather than
|
||||||
the simple alternative of just storing a variable directly on an object:
|
the simple alternative of just storing a variable directly on an object:
|
||||||
|
|
||||||
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
|
- NAttributes are tracked by Evennia and will not be purged in various cache-cleanup operations
|
||||||
the server may do. So using them guarantees that they'll remain available at least as long as
|
the server may do. So using them guarantees that they'll remain available at least as long as
|
||||||
the server lives.
|
the server lives.
|
||||||
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
|
- It's a consistent style - `.db/.attributes` and `.ndb/.nattributes` makes for clean-looking code
|
||||||
where it's clear how long-lived (or not) your data is to be.
|
where it's clear how long-lived (or not) your data is to be.
|
||||||
|
|
||||||
### Persistent vs non-persistent
|
### Persistent vs non-persistent
|
||||||
|
|
@ -557,4 +602,4 @@ useful in a few situations though.
|
||||||
- `NAttribute`s have no restrictions at all on what they can store, since they
|
- `NAttribute`s have no restrictions at all on what they can store, since they
|
||||||
don't need to worry about being saved to the database - they work very well for temporary storage.
|
don't need to worry about being saved to the database - they work very well for temporary storage.
|
||||||
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
|
- You want to implement a fully or partly *non-persistent world*. Who are we to argue with your
|
||||||
grand vision!
|
grand vision!
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,15 @@ the return from the function.
|
||||||
from evennia.utils.funcparser import FuncParser
|
from evennia.utils.funcparser import FuncParser
|
||||||
|
|
||||||
def _power_callable(*args, **kwargs):
|
def _power_callable(*args, **kwargs):
|
||||||
"""This will be callable as $square(number, power=<num>) in string"""
|
"""This will be callable as $pow(number, power=<num>) in string"""
|
||||||
pow = int(kwargs.get('power', 2))
|
pow = int(kwargs.get('power', 2))
|
||||||
return float(args[0]) ** pow
|
return float(args[0]) ** pow
|
||||||
|
|
||||||
|
# create a parser and tell it that '$pow' means using _power_callable
|
||||||
parser = FuncParser({"pow": _power_callable})
|
parser = FuncParser({"pow": _power_callable})
|
||||||
|
|
||||||
```
|
```
|
||||||
Next, just pass a string into the parser, optionally containing `$func(...)` markers:
|
Next, just pass a string into the parser, containing `$func(...)` markers:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
parser.parse("We have that 4 x 4 x 4 is $pow(4, power=3).")
|
parser.parse("We have that 4 x 4 x 4 is $pow(4, power=3).")
|
||||||
|
|
@ -71,7 +72,7 @@ You can apply inline function parsing to any string. The
|
||||||
from evennia.utils import funcparser
|
from evennia.utils import funcparser
|
||||||
|
|
||||||
parser = FuncParser(callables, **default_kwargs)
|
parser = FuncParser(callables, **default_kwargs)
|
||||||
parsed_string = parser.parser(input_string, raise_errors=False,
|
parsed_string = parser.parse(input_string, raise_errors=False,
|
||||||
escape=False, strip=False,
|
escape=False, strip=False,
|
||||||
return_str=True, **reserved_kwargs)
|
return_str=True, **reserved_kwargs)
|
||||||
|
|
||||||
|
|
@ -90,8 +91,12 @@ available to the parser as you parse strings with it. It can either be
|
||||||
an underscore `_`) will be considered a suitable callable. The name of the function will be the `$funcname`
|
an underscore `_`) will be considered a suitable callable. The name of the function will be the `$funcname`
|
||||||
by which it can be called.
|
by which it can be called.
|
||||||
- A `list` of modules/paths. This allows you to pull in modules from many sources for your parsing.
|
- A `list` of modules/paths. This allows you to pull in modules from many sources for your parsing.
|
||||||
|
- The `**default` kwargs are optional kwargs that will be passed to _all_
|
||||||
|
callables every time this parser is used - unless the user overrides it explicitly in
|
||||||
|
their call. This is great for providing sensible standards that the user can
|
||||||
|
tweak as needed.
|
||||||
|
|
||||||
The other arguments to the parser:
|
`FuncParser.parse` takes further arguments, and can vary for every string parsed.
|
||||||
|
|
||||||
- `raise_errors` - By default, any errors from a callable will be quietly ignored and the result
|
- `raise_errors` - By default, any errors from a callable will be quietly ignored and the result
|
||||||
will be that the failing function call will show verbatim. If `raise_errors` is set,
|
will be that the failing function call will show verbatim. If `raise_errors` is set,
|
||||||
|
|
@ -102,12 +107,14 @@ The other arguments to the parser:
|
||||||
- `return_str` - When `True` (default), `parser` always returns a string. If `False`, it may return
|
- `return_str` - When `True` (default), `parser` always returns a string. If `False`, it may return
|
||||||
the return value of a single function call in the string. This is the same as using the `.parse_to_any`
|
the return value of a single function call in the string. This is the same as using the `.parse_to_any`
|
||||||
method.
|
method.
|
||||||
- The `**default/reserved_keywords` are optional and allow you to pass custom data into _every_ function
|
- The `**reserved_keywords` are _always_ passed to every callable in the string.
|
||||||
call. This is great for including things like the current session or config options. Defaults can be
|
They override any `**defaults` given when instantiating the parser and cannot
|
||||||
replaced if the user gives the same-named kwarg in the string's function call. Reserved kwargs are always passed,
|
be overridden by the user - if they enter the same kwarg it will be ignored.
|
||||||
ignoring defaults or what the user passed. In addition, the `funcparser` and `raise_errors`
|
This is great for providing the current session, settings etc.
|
||||||
reserved kwargs are always passed - the first is a back-reference to the `FuncParser` instance and the second
|
- The `funcparser` and `raise_errors`
|
||||||
is the `raise_errors` boolean passed into `FuncParser.parse`.
|
are always added as reserved keywords - the first is a
|
||||||
|
back-reference to the `FuncParser` instance and the second
|
||||||
|
is the `raise_errors` boolean given to `FuncParser.parse`.
|
||||||
|
|
||||||
Here's an example of using the default/reserved keywords:
|
Here's an example of using the default/reserved keywords:
|
||||||
|
|
||||||
|
|
@ -158,7 +165,8 @@ created the parser.
|
||||||
|
|
||||||
However, if you _nest_ functions, the return of the innermost function may be something other than
|
However, if you _nest_ functions, the return of the innermost function may be something other than
|
||||||
a string. Let's introduce the `$eval` function, which evaluates simple expressions using
|
a string. Let's introduce the `$eval` function, which evaluates simple expressions using
|
||||||
Python's `literal_eval` and/or `simple_eval`.
|
Python's `literal_eval` and/or `simple_eval`. It returns whatever data type it
|
||||||
|
evaluates to.
|
||||||
|
|
||||||
"There's a $toint($eval(10 * 2.2))% chance of survival."
|
"There's a $toint($eval(10 * 2.2))% chance of survival."
|
||||||
|
|
||||||
|
|
@ -177,23 +185,66 @@ will be a string:
|
||||||
"There's a 22% chance of survival."
|
"There's a 22% chance of survival."
|
||||||
```
|
```
|
||||||
|
|
||||||
However, if you use the `parse_to_any` (or `parse(..., return_str=True)`) and _don't add any extra string around the outermost function call_,
|
However, if you use the `parse_to_any` (or `parse(..., return_str=False)`) and
|
||||||
|
_don't add any extra string around the outermost function call_,
|
||||||
you'll get the return type of the outermost callable back:
|
you'll get the return type of the outermost callable back:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
parser.parse_to_any("$toint($eval(10 * 2.2)%")
|
|
||||||
"22%"
|
|
||||||
parser.parse_to_any("$toint($eval(10 * 2.2)")
|
parser.parse_to_any("$toint($eval(10 * 2.2)")
|
||||||
22
|
22
|
||||||
|
parser.parse_to_any("the number $toint($eval(10 * 2.2).")
|
||||||
|
"the number 22"
|
||||||
|
parser.parse_to_any("$toint($eval(10 * 2.2)%")
|
||||||
|
"22%"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Escaping special character
|
||||||
|
|
||||||
|
When entering funcparser callables in strings, it looks like a regular
|
||||||
|
function call inside a string:
|
||||||
|
|
||||||
|
```python
|
||||||
|
"This is a $myfunc(arg1, arg2, kwarg=foo)."
|
||||||
|
```
|
||||||
|
|
||||||
|
Commas (`,`) and equal-signs (`=`) are considered to separate the arguments and
|
||||||
|
kwargs. In the same way, the right parenthesis (`)`) closes the argument list.
|
||||||
|
Sometimes you want to include commas in the argument without it breaking the
|
||||||
|
argument list.
|
||||||
|
|
||||||
|
```python
|
||||||
|
"There is a $format(beautiful meadow, with dandelions) to the west."
|
||||||
|
```
|
||||||
|
|
||||||
|
You can escape in various ways.
|
||||||
|
|
||||||
|
- Prepending with the escape character `\`
|
||||||
|
|
||||||
|
```python
|
||||||
|
"There is a $format(beautiful meadow\, with dandelions) to the west."
|
||||||
|
```
|
||||||
|
- Wrapping your strings in quotes. This works like Python, and you can nest
|
||||||
|
double and single quotes inside each other if so needed. The result will
|
||||||
|
be a verbatim string that contains everything but the outermost quotes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
"There is a $format('beautiful meadow, with dandelions') to the west."
|
||||||
|
```
|
||||||
|
- If you want verbatim quotes in your string, you can escape them too.
|
||||||
|
|
||||||
|
```python
|
||||||
|
"There is a $format('beautiful meadow, with \'dandelions\'') to the west."
|
||||||
|
```
|
||||||
|
|
||||||
### Safe convertion of inputs
|
### Safe convertion of inputs
|
||||||
|
|
||||||
Since you don't know in which order users may use your callables, they should always check the types
|
Since you don't know in which order users may use your callables, they should
|
||||||
of its inputs and convert to the type the callable needs. Note also that when converting from strings,
|
always check the types of its inputs and convert to the type the callable needs.
|
||||||
there are limits what inputs you can support. This is because FunctionParser strings are often used by
|
Note also that when converting from strings, there are limits what inputs you
|
||||||
non-developer players/builders and some things (such as complex classes/callables etc) are just not
|
can support. This is because FunctionParser strings can be used by
|
||||||
safe/possible to convert from string representation.
|
non-developer players/builders and some things (such as complex
|
||||||
|
classes/callables etc) are just not safe/possible to convert from string
|
||||||
|
representation.
|
||||||
|
|
||||||
In `evennia.utils.utils` is a helper called
|
In `evennia.utils.utils` is a helper called
|
||||||
[safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function
|
[safe_convert_to_types](evennia.utils.utils.safe_convert_to_types). This function
|
||||||
|
|
@ -204,19 +255,24 @@ from evennia.utils.utils import safe_convert_to_types
|
||||||
|
|
||||||
def _process_callable(*args, **kwargs):
|
def _process_callable(*args, **kwargs):
|
||||||
"""
|
"""
|
||||||
A callable with a lot of custom options
|
$process(expression, local, extra1=34, extra2=foo)
|
||||||
|
|
||||||
$process(expression, local, extra=34, extra2=foo)
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args, kwargs = safe_convert_to_type(
|
args, kwargs = safe_convert_to_type(
|
||||||
(('py', 'py'), {'extra1': int, 'extra2': str}),
|
(('py', str), {'extra1': int, 'extra2': str}),
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
|
||||||
# args/kwargs should be correct types now
|
# args/kwargs should be correct types now
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
In other words, in the callable `$process(expression, local, extra1=..,
|
||||||
|
extra2=...)`, the first argument will be handled by the 'py' converter
|
||||||
|
(described below), the second will passed through regular Python `str`,
|
||||||
|
kwargs will be handled by `int` and `str` respectively. You can supply
|
||||||
|
your own converter function as long as it takes one argument and returns
|
||||||
|
the converted result.
|
||||||
|
|
||||||
In other words,
|
In other words,
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
@ -224,8 +280,7 @@ args, kwargs = safe_convert_to_type(
|
||||||
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
|
(tuple_of_arg_converters, dict_of_kwarg_converters), *args, **kwargs)
|
||||||
```
|
```
|
||||||
|
|
||||||
Each converter should be a callable taking one argument - this will be the arg/kwarg-value to convert. The
|
The special converter `"py"` will try to convert a string argument to a Python structure with the help of the
|
||||||
special converter `"py"` will try to convert a string argument to a Python structure with the help of the
|
|
||||||
following tools (which you may also find useful to experiment with on your own):
|
following tools (which you may also find useful to experiment with on your own):
|
||||||
|
|
||||||
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python
|
- [ast.literal_eval](https://docs.python.org/3.8/library/ast.html#ast.literal_eval) is an in-built Python
|
||||||
|
|
@ -339,12 +394,12 @@ references to other objects accessible via these callables.
|
||||||
result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently
|
result of `you_obj.get_display_name(looker=receiver)`. This allows for a single string to echo differently
|
||||||
depending on who sees it, and also to reference other people in the same way.
|
depending on who sees it, and also to reference other people in the same way.
|
||||||
- `$You([key])` - same as `$you` but always capitalized.
|
- `$You([key])` - same as `$you` but always capitalized.
|
||||||
- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb
|
- `$conj(verb)` ([code](evennia.utils.funcparser.funcparser_callable_conjugate)) - conjugates a verb
|
||||||
between 2nd person presens to 3rd person presence depending on who
|
between 2nd person presens to 3rd person presence depending on who
|
||||||
sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending
|
sees the string. For example `"$You() $conj(smiles)".` will show as "You smile." and "Tom smiles." depending
|
||||||
on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](evennia.utils.verb_conjugation)
|
on who sees it. This makes use of the tools in [evennia.utils.verb_conjugation](evennia.utils.verb_conjugation)
|
||||||
to do this, and only works for English verbs.
|
to do this, and only works for English verbs.
|
||||||
- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically
|
- `$pron(pronoun [,options])` ([code](evennia.utils.funcparser.funcparser_callable_pronoun)) - Dynamically
|
||||||
map pronouns (like his, herself, you, its etc) between 1st/2nd person to 3rd person.
|
map pronouns (like his, herself, you, its etc) between 1st/2nd person to 3rd person.
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Evennia depends heavily on good documentation and we are always looking for
|
||||||
extra eyes and hands to improve it. Even small things such as fixing typos are a
|
extra eyes and hands to improve it. Even small things such as fixing typos are a
|
||||||
great help!
|
great help!
|
||||||
|
|
||||||
- Easiest is to just [report dicumentation issues][issues] as you find them. If
|
- Easiest is to just [report documentation issues][issues] as you find them. If
|
||||||
we don't know about them, we can't fix them!
|
we don't know about them, we can't fix them!
|
||||||
- If you want to help edit the docs directly, [check here](./Contributing-Docs.md)
|
- If you want to help edit the docs directly, [check here](./Contributing-Docs.md)
|
||||||
on how to do it.
|
on how to do it.
|
||||||
|
|
@ -83,7 +83,7 @@ like [Pastebin](https://pastebin.com/) and just supply the link.
|
||||||
Evennia has a [contrib](Contribs/Contribs-Overview.md) directory which contains
|
Evennia has a [contrib](Contribs/Contribs-Overview.md) directory which contains
|
||||||
user-shared code organized by category. You can contribute anything that you
|
user-shared code organized by category. You can contribute anything that you
|
||||||
think may be useful to another dev, also highly game-specific code. A contrib
|
think may be useful to another dev, also highly game-specific code. A contrib
|
||||||
must always be added via a forked respository.
|
must always be added via a forked repository.
|
||||||
|
|
||||||
#### Guidelines for making a contrib
|
#### Guidelines for making a contrib
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ must always be added via a forked respository.
|
||||||
documented and part of the installation instructions.
|
documented and part of the installation instructions.
|
||||||
- The contrib must be contained within a separate folder under one of the
|
- The contrib must be contained within a separate folder under one of the
|
||||||
contrib categories (`game_systems`, `rpg`, `utils` etc). Ask if you are
|
contrib categories (`game_systems`, `rpg`, `utils` etc). Ask if you are
|
||||||
unsuare which category to put your contrib under.
|
unsure which category to put your contrib under.
|
||||||
- The folder (package) should be on the following form:
|
- The folder (package) should be on the following form:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ Try to `look` at the box to see the (default) description.
|
||||||
|
|
||||||
The description you get is not very exciting. Let's add some flavor.
|
The description you get is not very exciting. Let's add some flavor.
|
||||||
|
|
||||||
describe box = This is a large and very heavy box.
|
desc box = This is a large and very heavy box.
|
||||||
|
|
||||||
If you try the `get` command we will pick up the box. So far so good, but if we really want this to
|
If you try the `get` command we will pick up the box. So far so good, but if we really want this to
|
||||||
be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent
|
be a large and heavy box, people should _not_ be able to run off with it that easily. To prevent
|
||||||
|
|
@ -155,10 +155,10 @@ later, in the [Commands tutorial](./Adding-Commands.md).
|
||||||
|
|
||||||
[Scripts](../../../Components/Scripts.md) are powerful out-of-character objects useful for many "under the hood" things.
|
[Scripts](../../../Components/Scripts.md) are powerful out-of-character objects useful for many "under the hood" things.
|
||||||
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
|
One of their optional abilities is to do things on a timer. To try out a first script, let's put one
|
||||||
on ourselves. There is an example script in `evennia/contrib/tutorial_examples/bodyfunctions.py`
|
on ourselves. There is an example script in `evennia/contrib/tutorials/bodyfunctions/bodyfunctions.py`
|
||||||
that is called `BodyFunctions`. To add this to us we will use the `script` command:
|
that is called `BodyFunctions`. To add this to us we will use the `script` command:
|
||||||
|
|
||||||
script self = tutorial_examples.bodyfunctions.BodyFunctions
|
script self = tutorials.bodyfunctions.BodyFunctions
|
||||||
|
|
||||||
This string will tell Evennia to dig up the Python code at the place we indicate. It already knows
|
This string will tell Evennia to dig up the Python code at the place we indicate. It already knows
|
||||||
to look in the `contrib/` folder, so we don't have to give the full path.
|
to look in the `contrib/` folder, so we don't have to give the full path.
|
||||||
|
|
@ -179,7 +179,7 @@ output every time it fires.
|
||||||
|
|
||||||
When you are tired of your character's "insights", kill the script with
|
When you are tired of your character's "insights", kill the script with
|
||||||
|
|
||||||
script/stop self = tutorial_examples.bodyfunctions.BodyFunctions
|
script/stop self = tutorials.bodyfunctions.BodyFunctions
|
||||||
|
|
||||||
You create your own scripts in Python, outside the game; the path you give to `script` is literally
|
You create your own scripts in Python, outside the game; the path you give to `script` is literally
|
||||||
the Python path to your script file. The [Scripts](../../../Components/Scripts.md) page explains more details.
|
the Python path to your script file. The [Scripts](../../../Components/Scripts.md) page explains more details.
|
||||||
|
|
@ -199,7 +199,7 @@ named simply `Object`. Let's create an object that is a little more interesting.
|
||||||
|
|
||||||
Let's make us one of _those_!
|
Let's make us one of _those_!
|
||||||
|
|
||||||
create/drop button:tutorial_examples.red_button.RedButton
|
create/drop button:tutorials.red_button.RedButton
|
||||||
|
|
||||||
The same way we did with the Script Earler, we specify a "Python-path" to the Python code we want Evennia
|
The same way we did with the Script Earler, we specify a "Python-path" to the Python code we want Evennia
|
||||||
to use for creating the object. There you go - one red button.
|
to use for creating the object. There you go - one red button.
|
||||||
|
|
@ -301,7 +301,7 @@ The Command-help is something you modify in Python code. We'll get to that when
|
||||||
add Commands. But you can also add regular help entries, for example to explain something about
|
add Commands. But you can also add regular help entries, for example to explain something about
|
||||||
the history of your game world:
|
the history of your game world:
|
||||||
|
|
||||||
sethelp/add History = At the dawn of time ...
|
sethelp History = At the dawn of time ...
|
||||||
|
|
||||||
You will now find your new `History` entry in the `help` list and read your help-text with `help History`.
|
You will now find your new `History` entry in the `help` list and read your help-text with `help History`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,137 +2,130 @@
|
||||||
|
|
||||||
*A list of resources that may be useful for Evennia users and developers.*
|
*A list of resources that may be useful for Evennia users and developers.*
|
||||||
|
|
||||||
## Official Evennia links
|
## Official Evennia resources
|
||||||
|
|
||||||
- [evennia.com](https://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia.
|
- [evennia.com](https://www.evennia.com) - Main Evennia portal page. Links to all corners of Evennia.
|
||||||
- [Evennia github page](https://github.com/evennia/evennia) - Download code and read documentation.
|
|
||||||
- [Evennia official chat
|
|
||||||
channel](https://webchat.freenode.net/?channels=evennia&uio=MT1mYWxzZSY5PXRydWUmMTE9MTk1JjEyPXRydWUbb)
|
|
||||||
- Our official IRC chat #evennia at irc.freenode.net:6667.
|
|
||||||
- [Evennia forums/mailing list](https://groups.google.com/group/evennia) - Web interface to our
|
|
||||||
google group.
|
|
||||||
- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer.
|
|
||||||
- [Evennia's manual on ReadTheDocs](https://readthedocs.org/projects/evennia/) - Read and download
|
- [Evennia's manual on ReadTheDocs](https://readthedocs.org/projects/evennia/) - Read and download
|
||||||
offline in html, PDF or epub formats.
|
offline in html, PDF or epub formats.
|
||||||
- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games.
|
- [Evennia development blog](https://evennia.blogspot.com/) - Musings from the lead developer.
|
||||||
----
|
- [Evennia on GitHub](https://github.com/evennia/evennia) - Download code and read documentation.
|
||||||
- [Evennia on Open Hub](https://www.openhub.net/p/6906)
|
- [Evennia on Open Hub](https://www.openhub.net/p/6906)
|
||||||
- [Evennia on OpenHatch](https://openhatch.org/projects/Evennia)
|
- [Evennia on OpenHatch](https://openhatch.org/projects/Evennia)
|
||||||
- [Evennia on PyPi](https://pypi.python.org/pypi/Evennia-MUD-Server/)
|
- [Evennia on PyPi](https://pypi.python.org/pypi/Evennia-MUD-Server/)
|
||||||
- [Evennia subreddit](https://www.reddit.com/r/Evennia/) (not much there yet though)
|
|
||||||
|
|
||||||
## Third-party Evennia utilities and resources
|
## Evennia Community
|
||||||
|
|
||||||
*For publicly available games running on Evennia, add and find those in the [Evennia game
|
- [Evennia Game Index](http://games.evennia.com/) - An automated listing of Evennia games.
|
||||||
index](http://games.evennia.com) instead!*
|
- [Evennia official Discord channel](https://discord.gg/AJJpcRUhtF)
|
||||||
|
- [Evennia official forums](https://github.com/evennia/evennia/discussions) on Github Discussions.
|
||||||
|
- [Evennia subreddit](https://www.reddit.com/r/Evennia/)
|
||||||
|
|
||||||
- [Discord Evennia channel](https://discord.gg/NecFePw) - This is a fan-driven Discord channel with
|
## Third-party Evennia tools
|
||||||
a bridge to the official Evennia IRC channel.
|
|
||||||
|
|
||||||
---
|
- [Discord relay](https://github.com/InspectorCaracal/evennia-things/tree/main/discord_relay) - Two-way chat relays between Evennia channels and Discord channels.
|
||||||
|
- [docker-compose for Evennia](https://github.com/gtaylor/evennia-docker) - A quick-install setup for running Evennia in a [Docker container](https://www.docker.com/). (See [the official Evennia docs](https://www.evennia.com/docs/latest/Running-Evennia-in-Docker.html) for more details on running Evennia with Docker.)
|
||||||
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the
|
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional coloration for Evennia unit-test output.
|
||||||
_Blackbirds_ Evennia game project.
|
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your website.
|
||||||
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin)
|
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
|
||||||
an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
|
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for telnet/web).
|
||||||
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON
|
- [Unreal Engine Evennia plugin](https://www.unrealengine.com/marketplace/en-US/slug/evennia-plugin) an in-progress Unreal plugin for integrating Evennia with Epic Games' Unreal Engine.
|
||||||
27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an
|
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`) files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia-
|
||||||
Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and
|
|
||||||
challenges [used during the conference](https://dcdark.net/home#).
|
|
||||||
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular
|
|
||||||
[Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](Arxcode-
|
|
||||||
installing-help)
|
|
||||||
- [Evennia-wiki](https://github.com/vincent-lg/evennia-wiki) - An Evennia-specific Wiki for your
|
|
||||||
website.
|
|
||||||
- [Evcolor](https://github.com/taladan/Pegasus/blob/origin/world/utilities/evcolor) - Optional
|
|
||||||
coloration for Evennia unit-test output.
|
|
||||||
- [Paxboards](https://github.com/aurorachain/paxboards) - Evennia bulletin board system (both for
|
|
||||||
telnet/web).
|
|
||||||
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for
|
|
||||||
Evennia with things like races, combat etc. [Summary
|
|
||||||
here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
|
|
||||||
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source
|
|
||||||
turn-based battle system for Evennia. It also has a [live demo](http://wcb.battlestudio.com/).
|
|
||||||
- [nextRPI](https://github.com/cluebyte/nextrpi) - A github project for making a toolbox for people
|
|
||||||
to make [RPI](https://www.topmudsites.com/forums/showthread.php?t=4804)-style Evennia games.
|
|
||||||
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an
|
|
||||||
older fork of Evennia. It has some specific design goals for building and extending the game based
|
|
||||||
on input files.
|
|
||||||
- [vim-evennia](https://github.com/amfl/vim-evennia) - A mode for editing batch-build files (`.ev`)
|
|
||||||
files in the [vim](https://www.vim.org/) text editor (Emacs users can use [evennia-
|
|
||||||
mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)).
|
mode.el](https://github.com/evennia/evennia/blob/master/evennia/utils/evennia-mode.el)).
|
||||||
|
- [The world of Cool battles sources](https://github.com/FlutterSprite/coolbattles) - Open source turn-based battle system for an older version of Evennia.
|
||||||
- [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia)
|
- [Other Evennia-related repos on github](https://github.com/search?p=1&q=evennia)
|
||||||
|
|
||||||
----
|
----
|
||||||
- [EvCast video series](https://www.youtube.com/playlist?list=PLyYMNttpc-SX1hvaqlUNmcxrhmM64pQXl) -
|
|
||||||
Tutorial videos explaining installing Evennia, basic Python etc.
|
|
||||||
- [Evennia-docker](https://github.com/gtaylor/evennia-docker) - Evennia in a [Docker
|
|
||||||
container](https://www.docker.com/) for quick install and deployment in just a few commands.
|
|
||||||
- [Evennia's docs in Chinese](http://www.evenniacn.com/) - A translated mirror of a slightly older
|
|
||||||
Evennia version. Announcement [here](https://groups.google.com/forum/#!topic/evennia/3AXS8ZTzJaA).
|
|
||||||
- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing
|
|
||||||
Evennia for those used to the MUSH way of doing things.
|
|
||||||
- *[Language Understanding for Text games using Deep reinforcement
|
|
||||||
learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)*
|
|
||||||
([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia
|
|
||||||
to train AIs.
|
|
||||||
|
|
||||||
## Other useful mud development resources
|
## Evennia-Based Projects
|
||||||
|
|
||||||
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to
|
### Code bases
|
||||||
Python objects.
|
- [Arx sources](https://github.com/Arx-Game/arxcode) - Open-source code release of the very popular [Arx](https://play.arxmush.org/) Evennia game. [Here are instructions for installing](https://www.evennia.com/docs/1.0-dev/Howtos/Arxcode-Installation.html)
|
||||||
- [Gossip MUD chat network](https://gossip.haus/)
|
- [Encarnia sources](https://github.com/whitehorse-io/encarnia) - An open-sourced game dir for an older version of Evennia with things like races, combat etc. [Summary here](https://www.reddit.com/r/MUD/comments/6z6s3j/encarnia_an_evennia_python_mud_code_base_with/).
|
||||||
|
- [The dark net/March Hare MUD](https://github.com/thedarknet/evennia) from the 2019 [DEF CON 27](https://www.defcon.org/html/defcon-27/dc-27-index.html) hacker conference in Paris. This is an Evennia game dir with batchcode to build the custom _Hackers_ style cyberspace zone with puzzles and challenges [used during the conference](https://dcdark.net/home#).
|
||||||
|
- [Muddery](https://github.com/muddery/muddery) - A mud framework under development, based on an older fork of Evennia. It has some specific design goals for building and extending the game based on input files.
|
||||||
|
|
||||||
## General MUD forums and discussions
|
### Other
|
||||||
|
|
||||||
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack
|
- [Discord live blog](https://discordapp.com/channels/517176782357528616/517176782781415434) of the _Blackbirds_ Evennia game project.
|
||||||
channel](https://slack.mudcoders.com/) with discussions on MUD development.
|
- [Evennia for MUSHers](https://musoapbox.net/topic/1150/evennia-for-mushers) - An article describing Evennia for those used to the MUSH way of doing things.
|
||||||
- [MuSoapbox](https://www.musoapbox.net/) - Very active Mu* game community mainly focused on MUSH-type gaming.
|
- *[Language Understanding for Text games using Deep reinforcement learning](http://news.mit.edu/2015/learning-language-playing-computer-games-0924#_msocom_1)*
|
||||||
- [Imaginary Realities](http://journal.imaginary-realities.com/) - An e-magazine on game and MUD
|
([PDF](https://people.csail.mit.edu/karthikn/pdfs/mud-play15.pdf)) - MIT research paper using Evennia to train AIs.
|
||||||
design that has several articles about Evennia. There is also an
|
|
||||||
[archive of older issues](http://disinterest.org/resource/imaginary-realities/)
|
----
|
||||||
from 1998-2001 that are still very relevant.
|
|
||||||
- [Optional Realities](http://optionalrealities.com/) - Mud development discussion forums that has
|
## General MU* resources
|
||||||
regular articles on MUD development focused on roleplay-intensive games. After a HD crash it's not
|
|
||||||
as content-rich as it once was.
|
### Tools
|
||||||
- [MudLab](http://mudlab.org/) - Mud design discussion forum
|
|
||||||
- [MudConnector](http://www.mudconnect.com/) - Mud listing and forums
|
- [ROM area reader](https://github.com/ctoth/area_reader) - Parser for converting ROM area files to Python objects.
|
||||||
- [MudBytes](http://www.mudbytes.net/) - Mud listing and forums
|
|
||||||
- [Top Mud Sites](http://www.topmudsites.com/) - Mud listing and forums
|
### Informational
|
||||||
- [Planet Mud-Dev](http://planet-muddev.disinterest.org/) - A blog aggregator following blogs of
|
|
||||||
current MUD development (including Evennia) around the 'net. Worth to put among your RSS
|
- [Imaginary Realities unofficial archive](http://tharsis-gate.org/articles/imaginary.html) - An e-magazine on game and MUD design that has several articles about Evennia.
|
||||||
subscriptions.
|
- [Lost Library of MOO](https://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in particular moo).
|
||||||
- Mud Dev mailing list archive ([mirror](http://www.disinterest.org/resource/MUD-Dev/)) -
|
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD telnet protocols.
|
||||||
Influential mailing list active 1996-2004. Advanced game design discussions.
|
|
||||||
- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation.
|
- [Mud-dev wiki](http://mud-dev.wikidot.com/) - A (very) slowly growing resource on MUD creation.
|
||||||
- [Mud Client/Server Interaction](http://cryosphere.net/mud-protocol.html) - A page on classic MUD
|
- [Mud Tech's fun/cool but ...](https://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) - Greg Taylor gives good advice on mud design.
|
||||||
telnet protocols.
|
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) - Contains a very useful list of things to think about when starting your new MUD.
|
||||||
- [Mud Tech's fun/cool but ...](https://gc-taylor.com/blog/2013/01/08/mud-tech-funcool-dont-forget-ship-damned-thing/) -
|
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) - thought-provoking guidelines and things to think about when designing a virtual multiplayer world (Raph is known for *Ultima Online* among other things).
|
||||||
Greg Taylor gives good advice on mud design.
|
|
||||||
- [Lost Library of MOO](https://www.hayseed.net/MOO/) - Archive of scientific articles on mudding (in
|
|
||||||
particular moo).
|
|
||||||
- [Nick Gammon's hints thread](http://www.gammon.com.au/forum/bbshowpost.php?bbsubject_id=5959) -
|
|
||||||
Contains a very useful list of things to think about when starting your new MUD.
|
|
||||||
- [Lost Garden](http://www.lostgarden.com/) - A game development blog with long and interesting
|
|
||||||
articles (not MUD-specific)
|
|
||||||
- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific)
|
|
||||||
- [The Alexandrian](https://thealexandrian.net/) - A blog about tabletop roleplaying and board games,
|
|
||||||
but with lots of general discussion about rule systems and game balance that could be applicable
|
|
||||||
also for MUDs.
|
|
||||||
- [Raph Koster's laws of game design](https://www.raphkoster.com/games/laws-of-online-world-design/the-laws-of-online-world-design/) -
|
|
||||||
thought-provoking guidelines and things to think about when designing a virtual multiplayer world
|
|
||||||
(Raph is known for *Ultima Online* among other things).
|
|
||||||
|
|
||||||
## Literature
|
### Community
|
||||||
|
|
||||||
|
- [Grapevine](https://grapevine.haus/) - MUD listings and inter-game chat network
|
||||||
|
- [MUD Coder's Guild](https://mudcoders.com/) - A blog and [associated Slack channel](https://slack.mudcoders.com/) with discussions on MUD development.
|
||||||
|
- [MudBytes](http://www.mudbytes.net/) - MUD listing and forums
|
||||||
|
- [MudConnector](http://www.mudconnect.com/) - MUD listing and forums
|
||||||
|
- [MudLab](http://mudlab.org/) - MUD design discussion forum
|
||||||
|
- [MuSoapbox](https://musoapbox.net/) - MU* forum mainly focused on MUSH-type gaming.
|
||||||
|
- [Top Mud Sites](http://www.topmudsites.com/) - MUD listing and forums
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## General Game-Dev Resources
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
- [GIT](https://git-scm.com/)
|
||||||
|
- [Documentation](https://git-scm.com/documentation)
|
||||||
|
- [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial)
|
||||||
|
|
||||||
|
### Frameworks
|
||||||
|
|
||||||
|
- [Django's homepage](https://www.djangoproject.com/)
|
||||||
|
- [Documentation](https://docs.djangoproject.com/en)
|
||||||
|
- [Code](https://code.djangoproject.com/)
|
||||||
|
- [Twisted homepage](https://twistedmatrix.com/)
|
||||||
|
- [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html)
|
||||||
|
- [Code](https://twistedmatrix.com/trac/browser)
|
||||||
|
|
||||||
|
### Learning Python
|
||||||
|
|
||||||
|
- [Python Website](https://www.python.org/)
|
||||||
|
- [Documentation](https://www.python.org/doc/)
|
||||||
|
- [Tutorial](https://docs.python.org/tut/tut.html)
|
||||||
|
- [Library Reference](https://docs.python.org/lib/lib.html)
|
||||||
|
- [Language Reference](https://docs.python.org/ref/ref.html)
|
||||||
|
- [Python tips and tricks](https://www.siafoo.net/article/52)
|
||||||
|
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) - free online programming curriculum for different skill levels
|
||||||
|
|
||||||
|
### Blogs
|
||||||
|
|
||||||
|
- [Lost Garden](https://lostgarden.home.blog/) - A game development blog with long and interesting articles (not MUD-specific)
|
||||||
|
- [What Games Are](http://whatgamesare.com/) - A blog about general game design (not MUD-specific)
|
||||||
|
- [The Alexandrian](https://thealexandrian.net/) - A blog about tabletop roleplaying and board games, but with lots of general discussion about rule systems and game balance that could be applicable also for MUDs.
|
||||||
|
|
||||||
|
### Literature
|
||||||
|
|
||||||
- Richard Bartle *Designing Virtual Worlds*
|
- Richard Bartle *Designing Virtual Worlds*
|
||||||
([amazon page](https://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) -
|
([amazon page](https://www.amazon.com/Designing-Virtual-Worlds-Richard-Bartle/dp/0131018167)) -
|
||||||
Essential reading for the design of any persistent game
|
Essential reading for the design of any persistent game
|
||||||
world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as
|
world, written by the co-creator of the original game *MUD*. Published in 2003 but it's still as
|
||||||
relevant now as when it came out. Covers everything you need to know and then some.
|
relevant now as when it came out. Covers everything you need to know and then some.
|
||||||
- Zed A. Shaw *Learn Python the Hard way* ([homepage](https://learnpythonthehardway.org/)) - Despite
|
|
||||||
the imposing name this book is for the absolute Python/programming beginner. One learns the language
|
When the rights to Designing Virtual Worlds returned to him, Richard Bartle
|
||||||
by gradually creating a small text game! It has been used by multiple users before moving on to
|
made the PDF of his Designing Virtual Worlds freely available through his own
|
||||||
Evennia. *Update: This used to be free to read online, this is no longer the case.*
|
website ([Designing Virtual Worlds](https://mud.co.uk/dvw/)). A direct link to
|
||||||
|
the PDF can be found [here](https://mud.co.uk/richard/DesigningVirtualWorlds.pdf).
|
||||||
- David M. Beazley *Python Essential Reference (4th ed)*
|
- David M. Beazley *Python Essential Reference (4th ed)*
|
||||||
([amazon page](https://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) -
|
([amazon page](https://www.amazon.com/Python-Essential-Reference-David-Beazley/dp/0672329786/)) -
|
||||||
Our recommended book on Python; it not only efficiently summarizes the language but is also
|
Our recommended book on Python; it not only efficiently summarizes the language but is also
|
||||||
|
|
@ -147,29 +140,3 @@ Contains a very useful list of things to think about when starting your new MUD.
|
||||||
economic theory. Written in 1730 but the translation is annotated and the essay is actually very
|
economic theory. Written in 1730 but the translation is annotated and the essay is actually very
|
||||||
easy to follow also for a modern reader. Required reading if you think of implementing a sane game
|
easy to follow also for a modern reader. Required reading if you think of implementing a sane game
|
||||||
economic system.
|
economic system.
|
||||||
|
|
||||||
## Frameworks
|
|
||||||
|
|
||||||
- [Django's homepage](https://www.djangoproject.com/)
|
|
||||||
- [Documentation](https://docs.djangoproject.com/en)
|
|
||||||
- [Code](https://code.djangoproject.com/)
|
|
||||||
- [Twisted homepage](https://twistedmatrix.com/)
|
|
||||||
- [Documentation](https://twistedmatrix.com/documents/current/core/howto/index.html)
|
|
||||||
- [Code](https://twistedmatrix.com/trac/browser)
|
|
||||||
|
|
||||||
## Tools
|
|
||||||
|
|
||||||
- [GIT](https://git-scm.com/)
|
|
||||||
- [Documentation](https://git-scm.com/documentation)
|
|
||||||
- [Learn GIT in 15 minutes](https://try.github.io/levels/1/challenges/1) (interactive tutorial)
|
|
||||||
|
|
||||||
## Python Info
|
|
||||||
|
|
||||||
- [Python Website](https://www.python.org/)
|
|
||||||
- [Documentation](https://www.python.org/doc/)
|
|
||||||
- [Tutorial](https://docs.python.org/tut/tut.html)
|
|
||||||
- [Library Reference](https://docs.python.org/lib/lib.html)
|
|
||||||
- [Language Reference](https://docs.python.org/ref/ref.html)
|
|
||||||
- [Python tips and tricks](https://www.siafoo.net/article/52)
|
|
||||||
- [Jetbrains Python academy](https://hyperskill.org/onboarding?track=python) -
|
|
||||||
free online programming curriculum for different skill levels
|
|
||||||
|
|
|
||||||
|
|
@ -678,7 +678,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
typeclass (str, optional): Typeclass to use for this character. If
|
typeclass (str, optional): Typeclass to use for this character. If
|
||||||
not given, use settings.BASE_CHARACTER_TYPECLASS.
|
not given, use settings.BASE_CHARACTER_TYPECLASS.
|
||||||
permissions (list, optional): If not given, use the account's permissions.
|
permissions (list, optional): If not given, use the account's permissions.
|
||||||
ip (str, optiona): The client IP creating this character. Will fall back to the
|
ip (str, optional): The client IP creating this character. Will fall back to the
|
||||||
one stored for the account if not given.
|
one stored for the account if not given.
|
||||||
kwargs (any): Other kwargs will be used in the create_call.
|
kwargs (any): Other kwargs will be used in the create_call.
|
||||||
Returns:
|
Returns:
|
||||||
|
|
@ -955,7 +955,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
kwargs (any): Other keyword arguments will be added to the
|
kwargs (any): Other keyword arguments will be added to the
|
||||||
found command object instance as variables before it
|
found command object instance as variables before it
|
||||||
executes. This is unused by default Evennia but may be
|
executes. This is unused by default Evennia but may be
|
||||||
used to set flags and change operating paramaters for
|
used to set flags and change operating parameters for
|
||||||
commands at run-time.
|
commands at run-time.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
@ -1433,7 +1433,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
|
self._send_to_connect_channel(_("|G{key} connected|n").format(key=self.key))
|
||||||
if _MULTISESSION_MODE == 0:
|
if _MULTISESSION_MODE == 0:
|
||||||
# in this mode we should have only one character available. We
|
# in this mode we should have only one character available. We
|
||||||
# try to auto-connect to our last conneted object, if any
|
# try to auto-connect to our last connected object, if any
|
||||||
try:
|
try:
|
||||||
self.puppet_object(session, self.db._last_puppet)
|
self.puppet_object(session, self.db._last_puppet)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|
@ -1460,7 +1460,7 @@ class DefaultAccount(AccountDB, metaclass=TypeclassBase):
|
||||||
"""
|
"""
|
||||||
Called by the login process if a user account is targeted correctly
|
Called by the login process if a user account is targeted correctly
|
||||||
but provided with an invalid password. By default it does nothing,
|
but provided with an invalid password. By default it does nothing,
|
||||||
but exists to be overriden.
|
but exists to be overridden.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
session (session): Session logging in.
|
session (session): Session logging in.
|
||||||
|
|
@ -1703,7 +1703,7 @@ class DefaultGuest(DefaultAccount):
|
||||||
Gets or creates a Guest account object.
|
Gets or creates a Guest account object.
|
||||||
|
|
||||||
Keyword Args:
|
Keyword Args:
|
||||||
ip (str, optional): IP address of requestor; used for ban checking,
|
ip (str, optional): IP address of requester; used for ban checking,
|
||||||
throttling and logging
|
throttling and logging
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
|
|
||||||
|
|
@ -450,9 +450,7 @@ class CmdSetHandler(object):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if "permanent" in kwargs:
|
if "permanent" in kwargs:
|
||||||
logger.log_dep(
|
logger.log_dep("obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'.")
|
||||||
"obj.cmdset.add() kwarg 'permanent' has changed name to 'persistent'."
|
|
||||||
)
|
|
||||||
persistent = kwargs["permanent"] if persistent is False else persistent
|
persistent = kwargs["permanent"] if persistent is False else persistent
|
||||||
|
|
||||||
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
if not (isinstance(cmdset, str) or utils.inherits_from(cmdset, CmdSet)):
|
||||||
|
|
|
||||||
|
|
@ -1071,7 +1071,7 @@ class CmdTunnel(COMMAND_DEFAULT_CLASS):
|
||||||
exitname, backshort = self.directions[exitshort]
|
exitname, backshort = self.directions[exitshort]
|
||||||
backname = self.directions[backshort][0]
|
backname = self.directions[backshort][0]
|
||||||
|
|
||||||
# if we recieved a typeclass for the exit, add it to the alias(short name)
|
# if we received a typeclass for the exit, add it to the alias(short name)
|
||||||
if ":" in self.lhs:
|
if ":" in self.lhs:
|
||||||
# limit to only the first : character
|
# limit to only the first : character
|
||||||
exit_typeclass = ":" + self.lhs.split(":", 1)[-1]
|
exit_typeclass = ":" + self.lhs.split(":", 1)[-1]
|
||||||
|
|
@ -1665,7 +1665,7 @@ class CmdSetAttribute(ObjManipCommand):
|
||||||
def split_nested_attr(self, attr):
|
def split_nested_attr(self, attr):
|
||||||
"""
|
"""
|
||||||
Yields tuples of (possible attr name, nested keys on that attr).
|
Yields tuples of (possible attr name, nested keys on that attr).
|
||||||
For performance, this is biased to the deepest match, but allows compatability
|
For performance, this is biased to the deepest match, but allows compatibility
|
||||||
with older attrs that might have been named with `[]`'s.
|
with older attrs that might have been named with `[]`'s.
|
||||||
|
|
||||||
> list(split_nested_attr("nested['asdf'][0]"))
|
> list(split_nested_attr("nested['asdf'][0]"))
|
||||||
|
|
@ -2219,11 +2219,13 @@ class CmdTypeclass(COMMAND_DEFAULT_CLASS):
|
||||||
old_typeclass_path = obj.typeclass_path
|
old_typeclass_path = obj.typeclass_path
|
||||||
|
|
||||||
if reset:
|
if reset:
|
||||||
answer = yield("|yNote that this will reset the object back to its typeclass' default state, "
|
answer = yield (
|
||||||
"removing any custom locks/perms/attributes etc that may have been added "
|
"|yNote that this will reset the object back to its typeclass' default state, "
|
||||||
"by an explicit create_object call. Use `update` or type/force instead in order "
|
"removing any custom locks/perms/attributes etc that may have been added "
|
||||||
"to keep such data. "
|
"by an explicit create_object call. Use `update` or type/force instead in order "
|
||||||
"Continue [Y]/N?|n")
|
"to keep such data. "
|
||||||
|
"Continue [Y]/N?|n"
|
||||||
|
)
|
||||||
if answer.upper() in ("N", "NO"):
|
if answer.upper() in ("N", "NO"):
|
||||||
caller.msg("Aborted.")
|
caller.msg("Aborted.")
|
||||||
return
|
return
|
||||||
|
|
@ -2732,7 +2734,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
return
|
return
|
||||||
|
|
||||||
if ndb_attr and ndb_attr[0]:
|
if ndb_attr and ndb_attr[0]:
|
||||||
return "\n " + " \n".join(
|
return "\n " + "\n ".join(
|
||||||
sorted(self.format_single_attribute(attr) for attr in ndb_attr)
|
sorted(self.format_single_attribute(attr) for attr in ndb_attr)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -2830,7 +2832,7 @@ class CmdExamine(ObjManipCommand):
|
||||||
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
objdata["Stored Cmdset(s)"] = self.format_stored_cmdsets(obj)
|
||||||
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
objdata["Merged Cmdset(s)"] = self.format_merged_cmdsets(obj, current_cmdset)
|
||||||
objdata[
|
objdata[
|
||||||
f"Commands vailable to {obj.key} (result of Merged Cmdset(s))"
|
f"Commands available to {obj.key} (result of Merged Cmdset(s))"
|
||||||
] = self.format_current_cmds(obj, current_cmdset)
|
] = self.format_current_cmds(obj, current_cmdset)
|
||||||
if self.object_type == "script":
|
if self.object_type == "script":
|
||||||
objdata["Description"] = self.format_script_desc(obj)
|
objdata["Description"] = self.format_script_desc(obj)
|
||||||
|
|
@ -3473,7 +3475,7 @@ class CmdScripts(COMMAND_DEFAULT_CLASS):
|
||||||
caller.msg("\n".join(msgs))
|
caller.msg("\n".join(msgs))
|
||||||
if "delete" not in self.switches:
|
if "delete" not in self.switches:
|
||||||
if script and script.pk:
|
if script and script.pk:
|
||||||
ScriptEvMore(caller, [script], session=self.session)
|
ScriptEvMore(caller, [script], session=self.session)
|
||||||
else:
|
else:
|
||||||
caller.msg("Script was deleted automatically.")
|
caller.msg("Script was deleted automatically.")
|
||||||
else:
|
else:
|
||||||
|
|
@ -4029,7 +4031,7 @@ class CmdSpawn(COMMAND_DEFAULT_CLASS):
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
# we homogenize the protoype first, to be more lenient with free-form
|
# we homogenize the prototype first, to be more lenient with free-form
|
||||||
protlib.validate_prototype(protlib.homogenize_prototype(prototype))
|
protlib.validate_prototype(protlib.homogenize_prototype(prototype))
|
||||||
except RuntimeError as err:
|
except RuntimeError as err:
|
||||||
self.caller.msg(str(err))
|
self.caller.msg(str(err))
|
||||||
|
|
|
||||||
|
|
@ -1818,7 +1818,7 @@ class CmdRSS2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
|
|
||||||
class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
|
class CmdGrapevine2Chan(COMMAND_DEFAULT_CLASS):
|
||||||
"""
|
"""
|
||||||
Link an Evennia channel to an exteral Grapevine channel
|
Link an Evennia channel to an external Grapevine channel
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
grapevine2chan[/switches] <evennia_channel> = <grapevine_channel>
|
grapevine2chan[/switches] <evennia_channel> = <grapevine_channel>
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
help <topic>/<subtopic>/<subsubtopic> ...
|
help <topic>/<subtopic>/<subsubtopic> ...
|
||||||
|
|
||||||
Use the 'help' command alone to see an index of all help topics, organized
|
Use the 'help' command alone to see an index of all help topics, organized
|
||||||
by category.eSome big topics may offer additional sub-topics.
|
by category. Some big topics may offer additional sub-topics.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -138,7 +138,7 @@ class CmdHelp(COMMAND_DEFAULT_CLASS):
|
||||||
click_topics=True,
|
click_topics=True,
|
||||||
):
|
):
|
||||||
"""This visually formats the help entry.
|
"""This visually formats the help entry.
|
||||||
This method can be overriden to customize the way a help
|
This method can be overridden to customize the way a help
|
||||||
entry is displayed.
|
entry is displayed.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,7 @@ class TestGeneral(BaseEvenniaCommandTest):
|
||||||
|
|
||||||
def test_nick_list(self):
|
def test_nick_list(self):
|
||||||
self.call(general.CmdNick(), "/list", "No nicks defined.")
|
self.call(general.CmdNick(), "/list", "No nicks defined.")
|
||||||
self.call(general.CmdNick(), "test1 = Hello",
|
self.call(general.CmdNick(), "test1 = Hello", "Inputline-nick 'test1' mapped to 'Hello'.")
|
||||||
"Inputline-nick 'test1' mapped to 'Hello'.")
|
|
||||||
self.call(general.CmdNick(), "/list", "Defined Nicks:")
|
self.call(general.CmdNick(), "/list", "Defined Nicks:")
|
||||||
|
|
||||||
def test_get_and_drop(self):
|
def test_get_and_drop(self):
|
||||||
|
|
@ -1295,7 +1294,8 @@ class TestBuilding(BaseEvenniaCommandTest):
|
||||||
"Obj2 = evennia.objects.objects.DefaultExit",
|
"Obj2 = evennia.objects.objects.DefaultExit",
|
||||||
"Obj2 changed typeclass from evennia.objects.objects.DefaultObject "
|
"Obj2 changed typeclass from evennia.objects.objects.DefaultObject "
|
||||||
"to evennia.objects.objects.DefaultExit.",
|
"to evennia.objects.objects.DefaultExit.",
|
||||||
cmdstring="swap", inputs=["yes"],
|
cmdstring="swap",
|
||||||
|
inputs=["yes"],
|
||||||
)
|
)
|
||||||
self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses")
|
self.call(building.CmdTypeclass(), "/list Obj", "Core typeclasses")
|
||||||
self.call(
|
self.call(
|
||||||
|
|
@ -1332,7 +1332,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
||||||
"/reset/force Obj=evennia.objects.objects.DefaultObject",
|
"/reset/force Obj=evennia.objects.objects.DefaultObject",
|
||||||
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
|
"Obj updated its existing typeclass (evennia.objects.objects.DefaultObject).\n"
|
||||||
"All object creation hooks were run. All old attributes where deleted before the swap.",
|
"All object creation hooks were run. All old attributes where deleted before the swap.",
|
||||||
inputs=["yes"]
|
inputs=["yes"],
|
||||||
)
|
)
|
||||||
|
|
||||||
from evennia.prototypes.prototypes import homogenize_prototype
|
from evennia.prototypes.prototypes import homogenize_prototype
|
||||||
|
|
@ -1359,7 +1359,7 @@ class TestBuilding(BaseEvenniaCommandTest):
|
||||||
"typeclasses.objects.Object.\nOnly the at_object_creation hook was run "
|
"typeclasses.objects.Object.\nOnly the at_object_creation hook was run "
|
||||||
"(update mode). Attributes set before swap were not removed\n"
|
"(update mode). Attributes set before swap were not removed\n"
|
||||||
"(use `swap` or `type/reset` to clear all). Prototype 'replaced_obj' was "
|
"(use `swap` or `type/reset` to clear all). Prototype 'replaced_obj' was "
|
||||||
"successfully applied over the object type."
|
"successfully applied over the object type.",
|
||||||
)
|
)
|
||||||
assert self.obj1.db.desc == "protdesc"
|
assert self.obj1.db.desc == "protdesc"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ def get_component_class(component_name):
|
||||||
subclasses = Component.__subclasses__()
|
subclasses = Component.__subclasses__()
|
||||||
component_class = next((sc for sc in subclasses if sc.name == component_name), None)
|
component_class = next((sc for sc in subclasses if sc.name == component_name), None)
|
||||||
if component_class is None:
|
if component_class is None:
|
||||||
message = f"Component named {component_name} has not been found. " \
|
message = (
|
||||||
f"Make sure it has been imported before being used."
|
f"Component named {component_name} has not been found. "
|
||||||
|
f"Make sure it has been imported before being used."
|
||||||
|
)
|
||||||
raise Exception(message)
|
raise Exception(message)
|
||||||
|
|
||||||
return component_class
|
return component_class
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ class Component:
|
||||||
|
|
||||||
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
Each Component must supply the name, it is used as a slot name but also part of the attribute key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
name = ""
|
name = ""
|
||||||
|
|
||||||
def __init__(self, host=None):
|
def __init__(self, host=None):
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ class DBField(AttributeProperty):
|
||||||
db_fields = getattr(owner, "_db_fields", None)
|
db_fields = getattr(owner, "_db_fields", None)
|
||||||
if db_fields is None:
|
if db_fields is None:
|
||||||
db_fields = {}
|
db_fields = {}
|
||||||
setattr(owner, '_db_fields', db_fields)
|
setattr(owner, "_db_fields", db_fields)
|
||||||
db_fields[name] = self
|
db_fields[name] = self
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ class NDBField(NAttributeProperty):
|
||||||
ndb_fields = getattr(owner, "_ndb_fields", None)
|
ndb_fields = getattr(owner, "_ndb_fields", None)
|
||||||
if ndb_fields is None:
|
if ndb_fields is None:
|
||||||
ndb_fields = {}
|
ndb_fields = {}
|
||||||
setattr(owner, '_ndb_fields', ndb_fields)
|
setattr(owner, "_ndb_fields", ndb_fields)
|
||||||
ndb_fields[name] = self
|
ndb_fields[name] = self
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -64,6 +64,7 @@ class TagField:
|
||||||
Default value of a tag is added when the component is registered.
|
Default value of a tag is added when the component is registered.
|
||||||
Tags are removed if the component itself is removed.
|
Tags are removed if the component itself is removed.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, default=None, enforce_single=False):
|
def __init__(self, default=None, enforce_single=False):
|
||||||
self._category_key = None
|
self._category_key = None
|
||||||
self._default = default
|
self._default = default
|
||||||
|
|
@ -78,7 +79,7 @@ class TagField:
|
||||||
tag_fields = getattr(owner, "_tag_fields", None)
|
tag_fields = getattr(owner, "_tag_fields", None)
|
||||||
if tag_fields is None:
|
if tag_fields is None:
|
||||||
tag_fields = {}
|
tag_fields = {}
|
||||||
setattr(owner, '_tag_fields', tag_fields)
|
setattr(owner, "_tag_fields", tag_fields)
|
||||||
tag_fields[name] = self
|
tag_fields[name] = self
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class ComponentProperty:
|
||||||
|
|
||||||
Defaults can be overridden for this typeclass by passing kwargs
|
Defaults can be overridden for this typeclass by passing kwargs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, component_name, **kwargs):
|
def __init__(self, component_name, **kwargs):
|
||||||
"""
|
"""
|
||||||
Initializes the descriptor
|
Initializes the descriptor
|
||||||
|
|
@ -49,6 +50,7 @@ class ComponentHandler:
|
||||||
It lets you add or remove components and will load components as needed.
|
It lets you add or remove components and will load components as needed.
|
||||||
It stores the list of registered components on the host .db with component_names as key.
|
It stores the list of registered components on the host .db with component_names as key.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, host):
|
def __init__(self, host):
|
||||||
self.host = host
|
self.host = host
|
||||||
self._loaded_components = {}
|
self._loaded_components = {}
|
||||||
|
|
@ -124,7 +126,9 @@ class ComponentHandler:
|
||||||
self.host.signals.remove_object_listeners_and_responders(component)
|
self.host.signals.remove_object_listeners_and_responders(component)
|
||||||
del self._loaded_components[component_name]
|
del self._loaded_components[component_name]
|
||||||
else:
|
else:
|
||||||
message = f"Cannot remove {component_name} from {self.host.name} as it is not registered."
|
message = (
|
||||||
|
f"Cannot remove {component_name} from {self.host.name} as it is not registered."
|
||||||
|
)
|
||||||
raise ComponentIsNotRegistered(message)
|
raise ComponentIsNotRegistered(message)
|
||||||
|
|
||||||
def remove_by_name(self, name):
|
def remove_by_name(self, name):
|
||||||
|
|
@ -199,7 +203,9 @@ class ComponentHandler:
|
||||||
self._set_component(component_instance)
|
self._set_component(component_instance)
|
||||||
self.host.signals.add_object_listeners_and_responders(component_instance)
|
self.host.signals.add_object_listeners_and_responders(component_instance)
|
||||||
else:
|
else:
|
||||||
message = f"Could not initialize runtime component {component_name} of {self.host.name}"
|
message = (
|
||||||
|
f"Could not initialize runtime component {component_name} of {self.host.name}"
|
||||||
|
)
|
||||||
raise ComponentDoesNotExist(message)
|
raise ComponentDoesNotExist(message)
|
||||||
|
|
||||||
def _set_component(self, component):
|
def _set_component(self, component):
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,11 @@ def as_listener(func=None, signal_name=None):
|
||||||
signal_name (str): The name of the signal to listen to, defaults to function name.
|
signal_name (str): The name of the signal to listen to, defaults to function name.
|
||||||
"""
|
"""
|
||||||
if not func and signal_name:
|
if not func and signal_name:
|
||||||
|
|
||||||
def wrapper(func):
|
def wrapper(func):
|
||||||
func._listener_signal_name = signal_name
|
func._listener_signal_name = signal_name
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
signal_name = func.__name__
|
signal_name = func.__name__
|
||||||
|
|
@ -35,9 +37,11 @@ def as_responder(func=None, signal_name=None):
|
||||||
signal_name (str): The name of the signal to respond to, defaults to function name.
|
signal_name (str): The name of the signal to respond to, defaults to function name.
|
||||||
"""
|
"""
|
||||||
if not func and signal_name:
|
if not func and signal_name:
|
||||||
|
|
||||||
def wrapper(func):
|
def wrapper(func):
|
||||||
func._responder_signal_name = signal_name
|
func._responder_signal_name = signal_name
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
signal_name = func.__name__
|
signal_name = func.__name__
|
||||||
|
|
@ -177,12 +181,12 @@ class SignalsHandler(object):
|
||||||
"""
|
"""
|
||||||
type_host = type(obj)
|
type_host = type(obj)
|
||||||
for att_name, att_obj in type_host.__dict__.items():
|
for att_name, att_obj in type_host.__dict__.items():
|
||||||
listener_signal_name = getattr(att_obj, '_listener_signal_name', None)
|
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||||
if listener_signal_name:
|
if listener_signal_name:
|
||||||
callback = getattr(obj, att_name)
|
callback = getattr(obj, att_name)
|
||||||
self.add_listener(signal_name=listener_signal_name, callback=callback)
|
self.add_listener(signal_name=listener_signal_name, callback=callback)
|
||||||
|
|
||||||
responder_signal_name = getattr(att_obj, '_responder_signal_name', None)
|
responder_signal_name = getattr(att_obj, "_responder_signal_name", None)
|
||||||
if responder_signal_name:
|
if responder_signal_name:
|
||||||
callback = getattr(obj, att_name)
|
callback = getattr(obj, att_name)
|
||||||
self.add_responder(signal_name=responder_signal_name, callback=callback)
|
self.add_responder(signal_name=responder_signal_name, callback=callback)
|
||||||
|
|
@ -196,12 +200,12 @@ class SignalsHandler(object):
|
||||||
"""
|
"""
|
||||||
type_host = type(obj)
|
type_host = type(obj)
|
||||||
for att_name, att_obj in type_host.__dict__.items():
|
for att_name, att_obj in type_host.__dict__.items():
|
||||||
listener_signal_name = getattr(att_obj, '_listener_signal_name', None)
|
listener_signal_name = getattr(att_obj, "_listener_signal_name", None)
|
||||||
if listener_signal_name:
|
if listener_signal_name:
|
||||||
callback = getattr(obj, att_name)
|
callback = getattr(obj, att_name)
|
||||||
self.remove_listener(signal_name=listener_signal_name, callback=callback)
|
self.remove_listener(signal_name=listener_signal_name, callback=callback)
|
||||||
|
|
||||||
responder_signal_name = getattr(att_obj, '_responder_signal_name', None)
|
responder_signal_name = getattr(att_obj, "_responder_signal_name", None)
|
||||||
if responder_signal_name:
|
if responder_signal_name:
|
||||||
callback = getattr(obj, att_name)
|
callback = getattr(obj, att_name)
|
||||||
self.remove_responder(signal_name=responder_signal_name, callback=callback)
|
self.remove_responder(signal_name=responder_signal_name, callback=callback)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class TestComponents(EvenniaTest):
|
||||||
def test_character_can_register_runtime_component(self):
|
def test_character_can_register_runtime_component(self):
|
||||||
rct = RuntimeComponentTestC.create(self.char1)
|
rct = RuntimeComponentTestC.create(self.char1)
|
||||||
self.char1.components.add(rct)
|
self.char1.components.add(rct)
|
||||||
test_c = self.char1.components.get('test_c')
|
test_c = self.char1.components.get("test_c")
|
||||||
|
|
||||||
assert test_c
|
assert test_c
|
||||||
assert test_c.my_int == 6
|
assert test_c.my_int == 6
|
||||||
|
|
@ -110,7 +110,7 @@ class TestComponents(EvenniaTest):
|
||||||
assert handler.get("test_c") is rct
|
assert handler.get("test_c") is rct
|
||||||
|
|
||||||
def test_can_access_component_regular_get(self):
|
def test_can_access_component_regular_get(self):
|
||||||
assert self.char1.cmp.test_a is self.char1.components.get('test_a')
|
assert self.char1.cmp.test_a is self.char1.components.get("test_a")
|
||||||
|
|
||||||
def test_returns_none_with_regular_get_when_no_attribute(self):
|
def test_returns_none_with_regular_get_when_no_attribute(self):
|
||||||
assert self.char1.cmp.does_not_exist is None
|
assert self.char1.cmp.does_not_exist is None
|
||||||
|
|
@ -127,7 +127,7 @@ class TestComponents(EvenniaTest):
|
||||||
def test_host_has_added_component_tags(self):
|
def test_host_has_added_component_tags(self):
|
||||||
rct = RuntimeComponentTestC.create(self.char1)
|
rct = RuntimeComponentTestC.create(self.char1)
|
||||||
self.char1.components.add(rct)
|
self.char1.components.add(rct)
|
||||||
test_c = self.char1.components.get('test_c')
|
test_c = self.char1.components.get("test_c")
|
||||||
|
|
||||||
assert self.char1.tags.has(key="test_c", category="components")
|
assert self.char1.tags.has(key="test_c", category="components")
|
||||||
assert self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
assert self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||||
|
|
@ -162,7 +162,7 @@ class TestComponents(EvenniaTest):
|
||||||
assert not self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
assert not self.char1.tags.has(key="added_value", category="test_c::added_tag")
|
||||||
|
|
||||||
def test_component_tags_only_hold_one_value_when_enforce_single(self):
|
def test_component_tags_only_hold_one_value_when_enforce_single(self):
|
||||||
test_b = self.char1.components.get('test_b')
|
test_b = self.char1.components.get("test_b")
|
||||||
test_b.single_tag = "first_value"
|
test_b.single_tag = "first_value"
|
||||||
test_b.single_tag = "second value"
|
test_b.single_tag = "second value"
|
||||||
|
|
||||||
|
|
@ -171,7 +171,7 @@ class TestComponents(EvenniaTest):
|
||||||
assert not self.char1.tags.has(key="first_value", category="test_b::single_tag")
|
assert not self.char1.tags.has(key="first_value", category="test_b::single_tag")
|
||||||
|
|
||||||
def test_component_tags_default_value_is_overridden_when_enforce_single(self):
|
def test_component_tags_default_value_is_overridden_when_enforce_single(self):
|
||||||
test_b = self.char1.components.get('test_b')
|
test_b = self.char1.components.get("test_b")
|
||||||
test_b.default_single_tag = "second value"
|
test_b.default_single_tag = "second value"
|
||||||
|
|
||||||
assert self.char1.tags.has(key="second value", category="test_b::default_single_tag")
|
assert self.char1.tags.has(key="second value", category="test_b::default_single_tag")
|
||||||
|
|
@ -179,12 +179,14 @@ class TestComponents(EvenniaTest):
|
||||||
assert not self.char1.tags.has(key="first_value", category="test_b::default_single_tag")
|
assert not self.char1.tags.has(key="first_value", category="test_b::default_single_tag")
|
||||||
|
|
||||||
def test_component_tags_support_multiple_values_by_default(self):
|
def test_component_tags_support_multiple_values_by_default(self):
|
||||||
test_b = self.char1.components.get('test_b')
|
test_b = self.char1.components.get("test_b")
|
||||||
test_b.multiple_tags = "first value"
|
test_b.multiple_tags = "first value"
|
||||||
test_b.multiple_tags = "second value"
|
test_b.multiple_tags = "second value"
|
||||||
test_b.multiple_tags = "third value"
|
test_b.multiple_tags = "third value"
|
||||||
|
|
||||||
assert all(val in test_b.multiple_tags for val in ("first value", "second value", "third value"))
|
assert all(
|
||||||
|
val in test_b.multiple_tags for val in ("first value", "second value", "third value")
|
||||||
|
)
|
||||||
assert self.char1.tags.has(key="first value", category="test_b::multiple_tags")
|
assert self.char1.tags.has(key="first value", category="test_b::multiple_tags")
|
||||||
assert self.char1.tags.has(key="second value", category="test_b::multiple_tags")
|
assert self.char1.tags.has(key="second value", category="test_b::multiple_tags")
|
||||||
assert self.char1.tags.has(key="third value", category="test_b::multiple_tags")
|
assert self.char1.tags.has(key="third value", category="test_b::multiple_tags")
|
||||||
|
|
@ -193,11 +195,11 @@ class TestComponents(EvenniaTest):
|
||||||
class CharWithSignal(ComponentHolderMixin, DefaultCharacter):
|
class CharWithSignal(ComponentHolderMixin, DefaultCharacter):
|
||||||
@signals.as_listener
|
@signals.as_listener
|
||||||
def my_signal(self):
|
def my_signal(self):
|
||||||
setattr(self, 'my_signal_is_called', True)
|
setattr(self, "my_signal_is_called", True)
|
||||||
|
|
||||||
@signals.as_listener
|
@signals.as_listener
|
||||||
def my_other_signal(self):
|
def my_other_signal(self):
|
||||||
setattr(self, 'my_other_signal_is_called', True)
|
setattr(self, "my_other_signal_is_called", True)
|
||||||
|
|
||||||
@signals.as_responder
|
@signals.as_responder
|
||||||
def my_response(self):
|
def my_response(self):
|
||||||
|
|
@ -213,11 +215,11 @@ class ComponentWithSignal(Component):
|
||||||
|
|
||||||
@signals.as_listener
|
@signals.as_listener
|
||||||
def my_signal(self):
|
def my_signal(self):
|
||||||
setattr(self, 'my_signal_is_called', True)
|
setattr(self, "my_signal_is_called", True)
|
||||||
|
|
||||||
@signals.as_listener
|
@signals.as_listener
|
||||||
def my_other_signal(self):
|
def my_other_signal(self):
|
||||||
setattr(self, 'my_other_signal_is_called', True)
|
setattr(self, "my_other_signal_is_called", True)
|
||||||
|
|
||||||
@signals.as_responder
|
@signals.as_responder
|
||||||
def my_response(self):
|
def my_response(self):
|
||||||
|
|
@ -236,14 +238,15 @@ class TestComponentSignals(BaseEvenniaTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super().setUp()
|
super().setUp()
|
||||||
self.char1 = create.create_object(
|
self.char1 = create.create_object(
|
||||||
CharWithSignal, key="Char",
|
CharWithSignal,
|
||||||
|
key="Char",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_host_can_register_as_listener(self):
|
def test_host_can_register_as_listener(self):
|
||||||
self.char1.signals.trigger("my_signal")
|
self.char1.signals.trigger("my_signal")
|
||||||
|
|
||||||
assert self.char1.my_signal_is_called
|
assert self.char1.my_signal_is_called
|
||||||
assert not getattr(self.char1, 'my_other_signal_is_called', None)
|
assert not getattr(self.char1, "my_other_signal_is_called", None)
|
||||||
|
|
||||||
def test_host_can_register_as_responder(self):
|
def test_host_can_register_as_responder(self):
|
||||||
responses = self.char1.signals.query("my_response")
|
responses = self.char1.signals.query("my_response")
|
||||||
|
|
@ -258,7 +261,7 @@ class TestComponentSignals(BaseEvenniaTest):
|
||||||
|
|
||||||
component = char.cmp.test_signal_a
|
component = char.cmp.test_signal_a
|
||||||
assert component.my_signal_is_called
|
assert component.my_signal_is_called
|
||||||
assert not getattr(component, 'my_other_signal_is_called', None)
|
assert not getattr(component, "my_other_signal_is_called", None)
|
||||||
|
|
||||||
def test_component_can_register_as_responder(self):
|
def test_component_can_register_as_responder(self):
|
||||||
char = self.char1
|
char = self.char1
|
||||||
|
|
|
||||||
|
|
@ -328,4 +328,4 @@ class GametimeScript(DefaultScript):
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
seconds = real_seconds_until(**self.db.gametime)
|
seconds = real_seconds_until(**self.db.gametime)
|
||||||
self.restart(interval=seconds)
|
self.start(interval=seconds, force_restart=True)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ Roleplaying emotes and language - Griatch, 2015
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa
|
from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa
|
||||||
from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa
|
|
||||||
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa
|
||||||
from .rpsystem import SdescHandler, RecogHandler # noqa
|
from .rpsystem import SdescHandler, RecogHandler # noqa
|
||||||
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -96,7 +96,7 @@ recog01 = "Mr Receiver"
|
||||||
recog02 = "Mr Receiver2"
|
recog02 = "Mr Receiver2"
|
||||||
recog10 = "Mr Sender"
|
recog10 = "Mr Sender"
|
||||||
emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."'
|
emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."'
|
||||||
case_emote = "/me looks at /first, then /FIRST, /First and /Colliding twice."
|
case_emote = "/Me looks at /first. Then, /me looks at /FIRST, /First and /Colliding twice."
|
||||||
|
|
||||||
|
|
||||||
class TestRPSystem(BaseEvenniaTest):
|
class TestRPSystem(BaseEvenniaTest):
|
||||||
|
|
@ -113,41 +113,11 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
rpsystem.ContribRPCharacter, key="Receiver2", location=self.room
|
rpsystem.ContribRPCharacter, key="Receiver2", location=self.room
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ordered_permutation_regex(self):
|
|
||||||
self.assertEqual(
|
|
||||||
rpsystem.ordered_permutation_regex(sdesc0),
|
|
||||||
"/[0-9]*-*A\\ nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*nice\\ sender\\ of\\ emotes(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*A\\ nice\\ sender\\ of(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*sender\\ of\\ emotes(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*nice\\ sender\\ of(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*A\\ nice\\ sender(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*nice\\ sender(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*of\\ emotes(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*sender\\ of(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*A\\ nice(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*emotes(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*sender(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*nice(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*of(?=\\W|$)+|"
|
|
||||||
"/[0-9]*-*A(?=\\W|$)+",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_sdesc_handler(self):
|
def test_sdesc_handler(self):
|
||||||
self.speaker.sdesc.add(sdesc0)
|
self.speaker.sdesc.add(sdesc0)
|
||||||
self.assertEqual(self.speaker.sdesc.get(), sdesc0)
|
self.assertEqual(self.speaker.sdesc.get(), sdesc0)
|
||||||
self.speaker.sdesc.add("This is {#324} ignored")
|
self.speaker.sdesc.add("This is {#324} ignored")
|
||||||
self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored")
|
self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored")
|
||||||
self.speaker.sdesc.add("Testing three words")
|
|
||||||
self.assertEqual(
|
|
||||||
self.speaker.sdesc.get_regex_tuple()[0].pattern,
|
|
||||||
"/[0-9]*-*Testing\ three\ words(?=\W|$)+|"
|
|
||||||
"/[0-9]*-*Testing\ three(?=\W|$)+|"
|
|
||||||
"/[0-9]*-*three\ words(?=\W|$)+|"
|
|
||||||
"/[0-9]*-*Testing(?=\W|$)+|"
|
|
||||||
"/[0-9]*-*three(?=\W|$)+|"
|
|
||||||
"/[0-9]*-*words(?=\W|$)+",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_recog_handler(self):
|
def test_recog_handler(self):
|
||||||
self.speaker.sdesc.add(sdesc0)
|
self.speaker.sdesc.add(sdesc0)
|
||||||
|
|
@ -156,12 +126,8 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
self.speaker.recog.add(self.receiver2, recog02)
|
self.speaker.recog.add(self.receiver2, recog02)
|
||||||
self.assertEqual(self.speaker.recog.get(self.receiver1), recog01)
|
self.assertEqual(self.speaker.recog.get(self.receiver1), recog01)
|
||||||
self.assertEqual(self.speaker.recog.get(self.receiver2), recog02)
|
self.assertEqual(self.speaker.recog.get(self.receiver2), recog02)
|
||||||
self.assertEqual(
|
|
||||||
self.speaker.recog.get_regex_tuple(self.receiver1)[0].pattern,
|
|
||||||
"/[0-9]*-*Mr\\ Receiver(?=\\W|$)+|/[0-9]*-*Receiver(?=\\W|$)+|/[0-9]*-*Mr(?=\\W|$)+",
|
|
||||||
)
|
|
||||||
self.speaker.recog.remove(self.receiver1)
|
self.speaker.recog.remove(self.receiver1)
|
||||||
self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1)
|
self.assertEqual(self.speaker.recog.get(self.receiver1), None)
|
||||||
|
|
||||||
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2})
|
||||||
|
|
||||||
|
|
@ -198,6 +164,26 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_sdesc(self):
|
||||||
|
looker = self.speaker # Sender
|
||||||
|
target = self.receiver1 # Receiver1
|
||||||
|
looker.sdesc.add(sdesc0) # A nice sender of emotes
|
||||||
|
target.sdesc.add(sdesc1) # The first receiver of emotes.
|
||||||
|
|
||||||
|
# sdesc with no processing
|
||||||
|
self.assertEqual(looker.get_sdesc(target), "The first receiver of emotes.")
|
||||||
|
# sdesc with processing
|
||||||
|
self.assertEqual(
|
||||||
|
looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n"
|
||||||
|
)
|
||||||
|
|
||||||
|
looker.recog.add(target, recog01) # Mr Receiver
|
||||||
|
|
||||||
|
# recog with no processing
|
||||||
|
self.assertEqual(looker.get_sdesc(target), "Mr Receiver")
|
||||||
|
# recog with processing
|
||||||
|
self.assertEqual(looker.get_sdesc(target, process=True), "|mMr Receiver|n")
|
||||||
|
|
||||||
def test_send_emote(self):
|
def test_send_emote(self):
|
||||||
speaker = self.speaker
|
speaker = self.speaker
|
||||||
receiver1 = self.receiver1
|
receiver1 = self.receiver1
|
||||||
|
|
@ -212,18 +198,18 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out0,
|
self.out0,
|
||||||
"With a flair, |bSender|n looks at |bThe first receiver of emotes.|n "
|
"With a flair, |mSender|n looks at |bThe first receiver of emotes.|n "
|
||||||
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out1,
|
self.out1,
|
||||||
"With a flair, |bA nice sender of emotes|n looks at |bReceiver1|n and "
|
"With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and "
|
||||||
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
'|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n',
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out2,
|
self.out2,
|
||||||
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
"With a flair, |bA nice sender of emotes|n looks at |bThe first "
|
||||||
'receiver of emotes.|n and |bReceiver2|n. She says |w"This is a test."|n',
|
'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n',
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_send_case_sensitive_emote(self):
|
def test_send_case_sensitive_emote(self):
|
||||||
|
|
@ -241,20 +227,21 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
rpsystem.send_emote(speaker, receivers, case_emote)
|
rpsystem.send_emote(speaker, receivers, case_emote)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out0,
|
self.out0,
|
||||||
"|bSender|n looks at |bthe first receiver of emotes.|n, then "
|
"|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n "
|
||||||
"|bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n and "
|
"looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n "
|
||||||
"|bAnother nice colliding sdesc-guy for tests|n twice.",
|
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out1,
|
self.out1,
|
||||||
"|bA nice sender of emotes|n looks at |bReceiver1|n, then |bReceiver1|n, "
|
"|bA nice sender of emotes|n looks at |mReceiver1|n. Then, "
|
||||||
"|bReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
"|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n "
|
||||||
|
"and |bAnother nice colliding sdesc-guy for tests|n twice.",
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.out2,
|
self.out2,
|
||||||
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n, "
|
"|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. "
|
||||||
"then |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of "
|
"Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, "
|
||||||
"emotes.|n and |bReceiver2|n twice.",
|
"|bThe first receiver of emotes.|n and |mReceiver2|n twice.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_rpsearch(self):
|
def test_rpsearch(self):
|
||||||
|
|
@ -265,18 +252,6 @@ class TestRPSystem(BaseEvenniaTest):
|
||||||
self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1)
|
self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1)
|
||||||
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
self.assertEqual(self.speaker.search("colliding"), self.receiver2)
|
||||||
|
|
||||||
def test_regex_tuple_from_key_alias(self):
|
|
||||||
self.speaker.aliases.add("foo bar")
|
|
||||||
self.speaker.aliases.add("this thing is a long thing")
|
|
||||||
t0 = time.time()
|
|
||||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
|
||||||
t1 = time.time()
|
|
||||||
result = rpsystem.regex_tuple_from_key_alias(self.speaker)
|
|
||||||
t2 = time.time()
|
|
||||||
# print(f"t1: {t1 - t0}, t2: {t2 - t1}")
|
|
||||||
self.assertLess(t2 - t1, 10**-4)
|
|
||||||
self.assertEqual(result, (Anything, self.speaker, self.speaker.key))
|
|
||||||
|
|
||||||
|
|
||||||
class TestRPSystemCommands(BaseEvenniaCommandTest):
|
class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
@ -305,7 +280,7 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||||
self.call(
|
self.call(
|
||||||
rpsystem.CmdRecog(),
|
rpsystem.CmdRecog(),
|
||||||
"barfoo as friend",
|
"barfoo as friend",
|
||||||
"Char will now remember BarFoo Character as friend.",
|
"You will now remember BarFoo Character as friend.",
|
||||||
)
|
)
|
||||||
self.call(
|
self.call(
|
||||||
rpsystem.CmdRecog(),
|
rpsystem.CmdRecog(),
|
||||||
|
|
@ -316,6 +291,6 @@ class TestRPSystemCommands(BaseEvenniaCommandTest):
|
||||||
self.call(
|
self.call(
|
||||||
rpsystem.CmdRecog(),
|
rpsystem.CmdRecog(),
|
||||||
"friend",
|
"friend",
|
||||||
"Char will now know them only as 'BarFoo Character'",
|
"You will now know them only as 'BarFoo Character'",
|
||||||
cmdstring="forget",
|
cmdstring="forget",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,6 @@ class TestTraitStatic(_TraitHandlerBase):
|
||||||
self.trait.mult = 0.75
|
self.trait.mult = 0.75
|
||||||
self.assertEqual(self._get_values(), (5, 1, 0.75, 4.5))
|
self.assertEqual(self._get_values(), (5, 1, 0.75, 4.5))
|
||||||
|
|
||||||
|
|
||||||
def test_delete(self):
|
def test_delete(self):
|
||||||
"""Deleting resets to default."""
|
"""Deleting resets to default."""
|
||||||
self.trait.mult = 2.0
|
self.trait.mult = 2.0
|
||||||
|
|
@ -362,7 +361,14 @@ class TestTraitCounter(_TraitHandlerBase):
|
||||||
|
|
||||||
def _get_values(self):
|
def _get_values(self):
|
||||||
"""Get (base, mod, mult, value, min, max)."""
|
"""Get (base, mod, mult, value, min, max)."""
|
||||||
return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max)
|
return (
|
||||||
|
self.trait.base,
|
||||||
|
self.trait.mod,
|
||||||
|
self.trait.mult,
|
||||||
|
self.trait.value,
|
||||||
|
self.trait.min,
|
||||||
|
self.trait.max,
|
||||||
|
)
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
@ -634,7 +640,14 @@ class TestTraitGauge(_TraitHandlerBase):
|
||||||
|
|
||||||
def _get_values(self):
|
def _get_values(self):
|
||||||
"""Get (base, mod, mult, value, min, max)."""
|
"""Get (base, mod, mult, value, min, max)."""
|
||||||
return (self.trait.base, self.trait.mod, self.trait.mult, self.trait.value, self.trait.min, self.trait.max)
|
return (
|
||||||
|
self.trait.base,
|
||||||
|
self.trait.mod,
|
||||||
|
self.trait.mult,
|
||||||
|
self.trait.value,
|
||||||
|
self.trait.min,
|
||||||
|
self.trait.max,
|
||||||
|
)
|
||||||
|
|
||||||
def test_init(self):
|
def test_init(self):
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
||||||
|
|
@ -1148,7 +1148,7 @@ class Trait:
|
||||||
|
|
||||||
class StaticTrait(Trait):
|
class StaticTrait(Trait):
|
||||||
"""
|
"""
|
||||||
Static Trait. This is a single value with a modifier,
|
Static Trait. This is a single value with a modifier,
|
||||||
multiplier, and no concept of a 'current' value or min/max etc.
|
multiplier, and no concept of a 'current' value or min/max etc.
|
||||||
|
|
||||||
value = (base + mod) * mult
|
value = (base + mod) * mult
|
||||||
|
|
@ -1161,7 +1161,9 @@ class StaticTrait(Trait):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = "{value:11}".format(value=self.value)
|
status = "{value:11}".format(value=self.value)
|
||||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult)
|
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(
|
||||||
|
name=self.name, status=status, mod=self.mod, mult=self.mult
|
||||||
|
)
|
||||||
|
|
||||||
# Helpers
|
# Helpers
|
||||||
@property
|
@property
|
||||||
|
|
@ -1189,7 +1191,7 @@ class StaticTrait(Trait):
|
||||||
def mult(self):
|
def mult(self):
|
||||||
"""The trait's multiplier."""
|
"""The trait's multiplier."""
|
||||||
return self._data["mult"]
|
return self._data["mult"]
|
||||||
|
|
||||||
@mult.setter
|
@mult.setter
|
||||||
def mult(self, amount):
|
def mult(self, amount):
|
||||||
if type(amount) in (int, float):
|
if type(amount) in (int, float):
|
||||||
|
|
@ -1322,16 +1324,16 @@ class CounterTrait(Trait):
|
||||||
now = time()
|
now = time()
|
||||||
tdiff = now - self._data["last_update"]
|
tdiff = now - self._data["last_update"]
|
||||||
current += rate * tdiff
|
current += rate * tdiff
|
||||||
value = (current + self.mod)
|
value = current + self.mod
|
||||||
|
|
||||||
# we must make sure so we don't overstep our bounds
|
# we must make sure so we don't overstep our bounds
|
||||||
# even if .mod is included
|
# even if .mod is included
|
||||||
|
|
||||||
if self._passed_ratetarget(value):
|
if self._passed_ratetarget(value):
|
||||||
current = (self._data["ratetarget"] - self.mod)
|
current = self._data["ratetarget"] - self.mod
|
||||||
self._stop_timer()
|
self._stop_timer()
|
||||||
elif not self._within_boundaries(value):
|
elif not self._within_boundaries(value):
|
||||||
current = (self._enforce_boundaries(value) - self.mod)
|
current = self._enforce_boundaries(value) - self.mod
|
||||||
self._stop_timer()
|
self._stop_timer()
|
||||||
else:
|
else:
|
||||||
self._data["last_update"] = now
|
self._data["last_update"] = now
|
||||||
|
|
@ -1378,7 +1380,7 @@ class CounterTrait(Trait):
|
||||||
@property
|
@property
|
||||||
def mult(self):
|
def mult(self):
|
||||||
return self._data["mult"]
|
return self._data["mult"]
|
||||||
|
|
||||||
@mult.setter
|
@mult.setter
|
||||||
def mult(self, amount):
|
def mult(self, amount):
|
||||||
if type(amount) in (int, float):
|
if type(amount) in (int, float):
|
||||||
|
|
@ -1571,7 +1573,9 @@ class GaugeTrait(CounterTrait):
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
status = "{value:4} / {base:4}".format(value=self.value, base=self.base)
|
status = "{value:4} / {base:4}".format(value=self.value, base=self.base)
|
||||||
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(name=self.name, status=status, mod=self.mod, mult=self.mult)
|
return "{name:12} {status} ({mod:+3}) (* {mult:.2f})".format(
|
||||||
|
name=self.name, status=status, mod=self.mod, mult=self.mult
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base(self):
|
def base(self):
|
||||||
|
|
@ -1596,11 +1600,11 @@ class GaugeTrait(CounterTrait):
|
||||||
if value + self.base < self.min:
|
if value + self.base < self.min:
|
||||||
value = self.min - self.base
|
value = self.min - self.base
|
||||||
self._data["mod"] = value
|
self._data["mod"] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mult(self):
|
def mult(self):
|
||||||
return self._data["mult"]
|
return self._data["mult"]
|
||||||
|
|
||||||
@mult.setter
|
@mult.setter
|
||||||
def mult(self, amount):
|
def mult(self, amount):
|
||||||
if type(amount) in (int, float):
|
if type(amount) in (int, float):
|
||||||
|
|
@ -1621,7 +1625,7 @@ class GaugeTrait(CounterTrait):
|
||||||
if value is None:
|
if value is None:
|
||||||
self._data["min"] = self.default_keys["min"]
|
self._data["min"] = self.default_keys["min"]
|
||||||
elif type(value) in (int, float):
|
elif type(value) in (int, float):
|
||||||
self._data["min"] = min(value, (self.base + self.mod) * self.mult)
|
self._data["min"] = min(value, (self.base + self.mod) * self.mult)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def max(self):
|
def max(self):
|
||||||
|
|
@ -1644,7 +1648,7 @@ class GaugeTrait(CounterTrait):
|
||||||
def current(self):
|
def current(self):
|
||||||
"""The `current` value of the gauge."""
|
"""The `current` value of the gauge."""
|
||||||
return self._update_current(
|
return self._update_current(
|
||||||
self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult))
|
self._enforce_boundaries(self._data.get("current", (self.base + self.mod) * self.mult))
|
||||||
)
|
)
|
||||||
|
|
||||||
@current.setter
|
@current.setter
|
||||||
|
|
@ -1655,7 +1659,7 @@ class GaugeTrait(CounterTrait):
|
||||||
@current.deleter
|
@current.deleter
|
||||||
def current(self):
|
def current(self):
|
||||||
"Resets current back to 'full'"
|
"Resets current back to 'full'"
|
||||||
self._data["current"] = (self.base + self.mod) * self.mult
|
self._data["current"] = (self.base + self.mod) * self.mult
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def value(self):
|
def value(self):
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# world/
|
# world/
|
||||||
|
|
||||||
This folder is meant as a miscellanous folder for all that other stuff
|
This folder is meant as a miscellaneous folder for all that other stuff
|
||||||
related to the game. Code which are not commands or typeclasses go
|
related to the game. Code which are not commands or typeclasses go
|
||||||
here, like custom economy systems, combat code, batch-files etc.
|
here, like custom economy systems, combat code, batch-files etc.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ Possible keywords are:
|
||||||
- `prototype_key` - the name of the prototype. This is required for db-prototypes,
|
- `prototype_key` - the name of the prototype. This is required for db-prototypes,
|
||||||
for module-prototypes, the global variable name of the dict is used instead
|
for module-prototypes, the global variable name of the dict is used instead
|
||||||
- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits
|
- `prototype_parent` - string pointing to parent prototype if any. Prototype inherits
|
||||||
in a similar way as classes, with children overriding values in their partents.
|
in a similar way as classes, with children overriding values in their parents.
|
||||||
- `key` - string, the main object identifier.
|
- `key` - string, the main object identifier.
|
||||||
- `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`.
|
- `typeclass` - string, if not set, will use `settings.BASE_OBJECT_TYPECLASS`.
|
||||||
- `location` - this should be a valid object or #dbref.
|
- `location` - this should be a valid object or #dbref.
|
||||||
|
|
@ -42,7 +42,7 @@ Possible keywords are:
|
||||||
of the shorter forms, defaults are used for the rest.
|
of the shorter forms, defaults are used for the rest.
|
||||||
- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`.
|
- `tags` - Tags, as a list of tuples `(tag,)`, `(tag, category)` or `(tag, category, data)`.
|
||||||
- Any other keywords are interpreted as Attributes with no category or lock.
|
- Any other keywords are interpreted as Attributes with no category or lock.
|
||||||
These will internally be added to `attrs` (eqivalent to `(attrname, value)`.
|
These will internally be added to `attrs` (equivalent to `(attrname, value)`.
|
||||||
|
|
||||||
See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info.
|
See the `spawn` command and `evennia.prototypes.spawner.spawn` for more info.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,13 @@ import re
|
||||||
# since we use them (e.g. as command names).
|
# since we use them (e.g. as command names).
|
||||||
# Lunr's default ignore-word list is found here:
|
# Lunr's default ignore-word list is found here:
|
||||||
# https://github.com/yeraydiazdiaz/lunr.py/blob/master/lunr/stop_word_filter.py
|
# https://github.com/yeraydiazdiaz/lunr.py/blob/master/lunr/stop_word_filter.py
|
||||||
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = (
|
_LUNR_STOP_WORD_FILTER_EXCEPTIONS = [
|
||||||
["about", "might", "get", "who", "say"] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS
|
"about",
|
||||||
)
|
"might",
|
||||||
|
"get",
|
||||||
|
"who",
|
||||||
|
"say",
|
||||||
|
] + settings.LUNR_STOP_WORD_FILTER_EXCEPTIONS
|
||||||
|
|
||||||
|
|
||||||
_LUNR = None
|
_LUNR = None
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,7 @@ def tag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
category = args[1] if len(args) > 1 else None
|
category = args[1] if len(args) > 1 else None
|
||||||
return bool(accessing_obj.tags.get(tagkey, category=category))
|
return bool(accessing_obj.tags.get(tagkey, category=category))
|
||||||
|
|
||||||
|
|
||||||
def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -489,13 +490,14 @@ def is_ooc(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
session = accessed_obj.session
|
session = accessed_obj.session
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
session = account.sessions.get()[0] # note-this doesn't work well
|
session = account.sessions.get()[0] # note-this doesn't work well
|
||||||
# for high multisession mode. We may need
|
# for high multisession mode. We may need
|
||||||
# to change to sessiondb to resolve this
|
# to change to sessiondb to resolve this
|
||||||
try:
|
try:
|
||||||
return not account.get_puppet(session)
|
return not account.get_puppet(session)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return not session.get_puppet()
|
return not session.get_puppet()
|
||||||
|
|
||||||
|
|
||||||
def objtag(accessing_obj, accessed_obj, *args, **kwargs):
|
def objtag(accessing_obj, accessed_obj, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Usage:
|
Usage:
|
||||||
|
|
|
||||||
|
|
@ -528,10 +528,10 @@ def search_prototype(
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# This will load the prototypes the first time they are searched
|
# This will load the prototypes the first time they are searched
|
||||||
loaded = getattr(load_module_prototypes, '_LOADED', False)
|
loaded = getattr(load_module_prototypes, "_LOADED", False)
|
||||||
if not loaded:
|
if not loaded:
|
||||||
load_module_prototypes()
|
load_module_prototypes()
|
||||||
setattr(load_module_prototypes, '_LOADED', True)
|
setattr(load_module_prototypes, "_LOADED", True)
|
||||||
|
|
||||||
# prototype keys are always in lowecase
|
# prototype keys are always in lowecase
|
||||||
if key:
|
if key:
|
||||||
|
|
|
||||||
|
|
@ -2316,9 +2316,11 @@ def main():
|
||||||
if option in ("makemessages", "compilemessages"):
|
if option in ("makemessages", "compilemessages"):
|
||||||
# some commands don't require the presence of a game directory to work
|
# some commands don't require the presence of a game directory to work
|
||||||
need_gamedir = False
|
need_gamedir = False
|
||||||
if CURRENT_DIR != EVENNIA_LIB:
|
if CURRENT_DIR != EVENNIA_LIB:
|
||||||
print("You must stand in the evennia/evennia/ folder (where the 'locale/' "
|
print(
|
||||||
"folder is located) to run this command.")
|
"You must stand in the evennia/evennia/ folder (where the 'locale/' "
|
||||||
|
"folder is located) to run this command."
|
||||||
|
)
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if option in ("shell", "check", "makemigrations", "createsuperuser", "shell_plus"):
|
if option in ("shell", "check", "makemigrations", "createsuperuser", "shell_plus"):
|
||||||
|
|
|
||||||
|
|
@ -226,6 +226,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
or option == naws.NAWS
|
or option == naws.NAWS
|
||||||
or option == MCCP
|
or option == MCCP
|
||||||
or option == mssp.MSSP
|
or option == mssp.MSSP
|
||||||
|
or option == ECHO
|
||||||
or option == suppress_ga.SUPPRESS_GA
|
or option == suppress_ga.SUPPRESS_GA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -236,6 +237,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, _BASE_SESSION_CLASS):
|
||||||
or option == naws.NAWS
|
or option == naws.NAWS
|
||||||
or option == MCCP
|
or option == MCCP
|
||||||
or option == mssp.MSSP
|
or option == mssp.MSSP
|
||||||
|
or option == ECHO
|
||||||
or option == suppress_ga.SUPPRESS_GA
|
or option == suppress_ga.SUPPRESS_GA
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,7 @@ class Evennia:
|
||||||
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
|
logger.log_msg("Evennia Server successfully restarted in 'reset' mode.")
|
||||||
elif mode == "shutdown":
|
elif mode == "shutdown":
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
|
|
||||||
self.at_server_cold_start()
|
self.at_server_cold_start()
|
||||||
# clear eventual lingering session storages
|
# clear eventual lingering session storages
|
||||||
ObjectDB.objects.clear_all_sessids()
|
ObjectDB.objects.clear_all_sessids()
|
||||||
|
|
|
||||||
|
|
@ -683,7 +683,7 @@ class ServerSessionHandler(SessionHandler):
|
||||||
Get a unique list of connected and logged-in Accounts.
|
Get a unique list of connected and logged-in Accounts.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
accounts (list): All conected Accounts (which may be fewer than the
|
accounts (list): All connected Accounts (which may be fewer than the
|
||||||
amount of Sessions due to multi-playing).
|
amount of Sessions due to multi-playing).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,6 @@ class TestServer(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class TestInitHooks(TestCase):
|
class TestInitHooks(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
||||||
from evennia.utils import create
|
from evennia.utils import create
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,18 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
|
||||||
avoid running the large number of tests defined by Django
|
avoid running the large number of tests defined by Django
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setup_test_environment(self, **kwargs):
|
def setup_test_environment(self, **kwargs):
|
||||||
# the portal looping call starts before the unit-test suite so we
|
# the portal looping call starts before the unit-test suite so we
|
||||||
# can't mock it - instead we stop it before starting the test - otherwise
|
# can't mock it - instead we stop it before starting the test - otherwise
|
||||||
# we'd get unclean reactor errors across test boundaries.
|
# we'd get unclean reactor errors across test boundaries.
|
||||||
from evennia.server.portal.portal import PORTAL
|
from evennia.server.portal.portal import PORTAL
|
||||||
|
|
||||||
PORTAL.maintenance_task.stop()
|
PORTAL.maintenance_task.stop()
|
||||||
|
|
||||||
# initialize evennia itself
|
# initialize evennia itself
|
||||||
import evennia
|
import evennia
|
||||||
|
|
||||||
evennia._init()
|
evennia._init()
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
@ -37,6 +40,7 @@ class EvenniaTestSuiteRunner(DiscoverRunner):
|
||||||
|
|
||||||
# remove testing flag after suite has run
|
# remove testing flag after suite has run
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
settings._TEST_ENVIRONMENT = False
|
settings._TEST_ENVIRONMENT = False
|
||||||
|
|
||||||
super().teardown_test_environment(**kwargs)
|
super().teardown_test_environment(**kwargs)
|
||||||
|
|
|
||||||
|
|
@ -218,6 +218,7 @@ class AttributeProperty:
|
||||||
"""
|
"""
|
||||||
value = self._default
|
value = self._default
|
||||||
try:
|
try:
|
||||||
|
<<<<<<< HEAD
|
||||||
value = self.at_get(getattr(instance, self.attrhandler_name).get(
|
value = self.at_get(getattr(instance, self.attrhandler_name).get(
|
||||||
key=self._key,
|
key=self._key,
|
||||||
default=self._default,
|
default=self._default,
|
||||||
|
|
@ -225,6 +226,17 @@ class AttributeProperty:
|
||||||
strattr=self._strattr,
|
strattr=self._strattr,
|
||||||
raise_exception=self._autocreate,
|
raise_exception=self._autocreate,
|
||||||
), instance)
|
), instance)
|
||||||
|
=======
|
||||||
|
value = self.at_get(
|
||||||
|
getattr(instance, self.attrhandler_name).get(
|
||||||
|
key=self._key,
|
||||||
|
default=self._default,
|
||||||
|
category=self._category,
|
||||||
|
strattr=self._strattr,
|
||||||
|
raise_exception=self._autocreate,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
>>>>>>> ce3992f999a164881462d8f878d71a47a8f946cc
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
if self._autocreate:
|
if self._autocreate:
|
||||||
# attribute didn't exist and autocreate is set
|
# attribute didn't exist and autocreate is set
|
||||||
|
|
|
||||||
|
|
@ -286,7 +286,7 @@ class TypedObjectManager(idmapper.manager.SharedMemoryManager):
|
||||||
categories = make_iter(category) if category else []
|
categories = make_iter(category) if category else []
|
||||||
n_keys = len(keys)
|
n_keys = len(keys)
|
||||||
n_categories = len(categories)
|
n_categories = len(categories)
|
||||||
unique_categories = sorted(set(categories))
|
unique_categories = set(categories)
|
||||||
n_unique_categories = len(unique_categories)
|
n_unique_categories = len(unique_categories)
|
||||||
|
|
||||||
dbmodel = self.model.__dbclass__.__name__.lower()
|
dbmodel = self.model.__dbclass__.__name__.lower()
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ class Tag(models.Model):
|
||||||
# Handlers making use of the Tags model
|
# Handlers making use of the Tags model
|
||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
class TagProperty:
|
class TagProperty:
|
||||||
"""
|
"""
|
||||||
Tag property descriptor. Allows for setting tags on an object as Django-like 'fields'
|
Tag property descriptor. Allows for setting tags on an object as Django-like 'fields'
|
||||||
|
|
@ -112,6 +113,7 @@ class TagProperty:
|
||||||
mytag2 = TagProperty(category="tagcategory")
|
mytag2 = TagProperty(category="tagcategory")
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
taghandler_name = "tags"
|
taghandler_name = "tags"
|
||||||
|
|
||||||
def __init__(self, category=None, data=None):
|
def __init__(self, category=None, data=None):
|
||||||
|
|
@ -134,10 +136,7 @@ class TagProperty:
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return getattr(instance, self.taghandler_name).get(
|
return getattr(instance, self.taghandler_name).get(
|
||||||
key=self._key,
|
key=self._key, category=self._category, return_list=False, raise_exception=True
|
||||||
category=self._category,
|
|
||||||
return_list=False,
|
|
||||||
raise_exception=True
|
|
||||||
)
|
)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.__set__(instance, self._category)
|
self.__set__(instance, self._category)
|
||||||
|
|
@ -150,9 +149,7 @@ class TagProperty:
|
||||||
self._category = category
|
self._category = category
|
||||||
(
|
(
|
||||||
getattr(instance, self.taghandler_name).add(
|
getattr(instance, self.taghandler_name).add(
|
||||||
key=self._key,
|
key=self._key, category=self._category, data=self._data
|
||||||
category=self._category,
|
|
||||||
data=self._data
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -430,8 +427,15 @@ class TagHandler(object):
|
||||||
|
|
||||||
return ret[0] if len(ret) == 1 else ret
|
return ret[0] if len(ret) == 1 else ret
|
||||||
|
|
||||||
def get(self, key=None, default=None, category=None, return_tagobj=False, return_list=False,
|
def get(
|
||||||
raise_exception=False):
|
self,
|
||||||
|
key=None,
|
||||||
|
default=None,
|
||||||
|
category=None,
|
||||||
|
return_tagobj=False,
|
||||||
|
return_list=False,
|
||||||
|
raise_exception=False,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Get the tag for the given key, category or combination of the two.
|
Get the tag for the given key, category or combination of the two.
|
||||||
|
|
||||||
|
|
@ -613,6 +617,7 @@ class AliasProperty(TagProperty):
|
||||||
bob = AliasProperty()
|
bob = AliasProperty()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
taghandler_name = "aliases"
|
taghandler_name = "aliases"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -636,6 +641,7 @@ class PermissionProperty(TagProperty):
|
||||||
myperm = PermissionProperty()
|
myperm = PermissionProperty()
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
taghandler_name = "permissions"
|
taghandler_name = "permissions"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,15 @@ class TestTypedObjectManager(BaseEvenniaTest):
|
||||||
[self.obj1],
|
[self.obj1],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_tag_with_any_including_nones(self):
|
||||||
|
self.obj1.tags.add("tagA", "categoryA")
|
||||||
|
self.assertEqual(
|
||||||
|
self._manager(
|
||||||
|
"get_by_tag", ["tagA", "tagB"], ["categoryA", "categoryB", None], match="any"
|
||||||
|
),
|
||||||
|
[self.obj1],
|
||||||
|
)
|
||||||
|
|
||||||
def test_get_tag_withnomatch(self):
|
def test_get_tag_withnomatch(self):
|
||||||
self.obj1.tags.add("tagC", "categoryC")
|
self.obj1.tags.add("tagC", "categoryC")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ evennia.OPTION_CLASSES
|
||||||
|
|
||||||
|
|
||||||
from pickle import dumps
|
from pickle import dumps
|
||||||
|
from django.db.utils import OperationalError, ProgrammingError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia.utils.utils import class_from_module, callables_from_module
|
from evennia.utils.utils import class_from_module, callables_from_module
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
@ -167,7 +168,6 @@ class GlobalScriptContainer(Container):
|
||||||
|
|
||||||
# store a hash representation of the setup
|
# store a hash representation of the setup
|
||||||
script.attributes.add("_global_script_settings", compare_hash, category="settings_hash")
|
script.attributes.add("_global_script_settings", compare_hash, category="settings_hash")
|
||||||
script.start()
|
|
||||||
|
|
||||||
return script
|
return script
|
||||||
|
|
||||||
|
|
@ -183,9 +183,16 @@ class GlobalScriptContainer(Container):
|
||||||
# populate self.typeclass_storage
|
# populate self.typeclass_storage
|
||||||
self.load_data()
|
self.load_data()
|
||||||
|
|
||||||
# start registered scripts
|
# make sure settings-defined scripts are loaded
|
||||||
for key in self.loaded_data:
|
for key in self.loaded_data:
|
||||||
self._load_script(key)
|
self._load_script(key)
|
||||||
|
# start all global scripts
|
||||||
|
try:
|
||||||
|
for script in self._get_scripts():
|
||||||
|
script.start()
|
||||||
|
except (OperationalError, ProgrammingError):
|
||||||
|
# this can happen if db is not loaded yet (such as when building docs)
|
||||||
|
pass
|
||||||
|
|
||||||
def load_data(self):
|
def load_data(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from collections import deque, OrderedDict, defaultdict
|
||||||
from collections.abc import MutableSequence, MutableSet, MutableMapping
|
from collections.abc import MutableSequence, MutableSet, MutableMapping
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pickle import dumps, loads
|
from pickle import dumps, loads, UnpicklingError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
|
@ -239,6 +239,9 @@ class _SaverMutable(object):
|
||||||
def __gt__(self, other):
|
def __gt__(self, other):
|
||||||
return self._data > other
|
return self._data > other
|
||||||
|
|
||||||
|
def __or__(self, other):
|
||||||
|
return self._data | other
|
||||||
|
|
||||||
@_save
|
@_save
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
self._data.__setitem__(key, self._convert_mutables(value))
|
self._data.__setitem__(key, self._convert_mutables(value))
|
||||||
|
|
@ -450,7 +453,9 @@ def deserialize(obj):
|
||||||
elif tname in ("_SaverOrderedDict", "OrderedDict"):
|
elif tname in ("_SaverOrderedDict", "OrderedDict"):
|
||||||
return OrderedDict([(_iter(key), _iter(val)) for key, val in obj.items()])
|
return OrderedDict([(_iter(key), _iter(val)) for key, val in obj.items()])
|
||||||
elif tname in ("_SaverDefaultDict", "defaultdict"):
|
elif tname in ("_SaverDefaultDict", "defaultdict"):
|
||||||
return defaultdict(obj.default_factory, {_iter(key): _iter(val) for key, val in obj.items()})
|
return defaultdict(
|
||||||
|
obj.default_factory, {_iter(key): _iter(val) for key, val in obj.items()}
|
||||||
|
)
|
||||||
elif tname in _DESERIALIZE_MAPPING:
|
elif tname in _DESERIALIZE_MAPPING:
|
||||||
return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj)
|
return _DESERIALIZE_MAPPING[tname](_iter(val) for val in obj)
|
||||||
elif is_iter(obj):
|
elif is_iter(obj):
|
||||||
|
|
@ -602,7 +607,9 @@ def to_pickle(data):
|
||||||
|
|
||||||
def process_item(item):
|
def process_item(item):
|
||||||
"""Recursive processor and identification of data"""
|
"""Recursive processor and identification of data"""
|
||||||
|
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
|
|
||||||
if dtype in (str, int, float, bool, bytes, SafeString):
|
if dtype in (str, int, float, bool, bytes, SafeString):
|
||||||
return item
|
return item
|
||||||
elif dtype == tuple:
|
elif dtype == tuple:
|
||||||
|
|
@ -612,7 +619,10 @@ def to_pickle(data):
|
||||||
elif dtype in (dict, _SaverDict):
|
elif dtype in (dict, _SaverDict):
|
||||||
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
||||||
elif dtype in (defaultdict, _SaverDefaultDict):
|
elif dtype in (defaultdict, _SaverDefaultDict):
|
||||||
return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items()))
|
return defaultdict(
|
||||||
|
item.default_factory,
|
||||||
|
((process_item(key), process_item(val)) for key, val in item.items()),
|
||||||
|
)
|
||||||
elif dtype in (set, _SaverSet):
|
elif dtype in (set, _SaverSet):
|
||||||
return set(process_item(val) for val in item)
|
return set(process_item(val) for val in item)
|
||||||
elif dtype in (OrderedDict, _SaverOrderedDict):
|
elif dtype in (OrderedDict, _SaverOrderedDict):
|
||||||
|
|
@ -620,7 +630,20 @@ def to_pickle(data):
|
||||||
elif dtype in (deque, _SaverDeque):
|
elif dtype in (deque, _SaverDeque):
|
||||||
return deque(process_item(val) for val in item)
|
return deque(process_item(val) for val in item)
|
||||||
|
|
||||||
elif hasattr(item, "__iter__"):
|
# not one of the base types
|
||||||
|
if hasattr(item, "__serialize_dbobjs__"):
|
||||||
|
# Allows custom serialization of any dbobjects embedded in
|
||||||
|
# the item that Evennia will otherwise not find (these would
|
||||||
|
# otherwise lead to an error). Use the dbserialize helper from
|
||||||
|
# this method.
|
||||||
|
try:
|
||||||
|
item.__serialize_dbobjs__()
|
||||||
|
except TypeError as err:
|
||||||
|
# we catch typerrors so we can handle both classes (requiring
|
||||||
|
# classmethods) and instances
|
||||||
|
pass
|
||||||
|
|
||||||
|
if hasattr(item, "__iter__"):
|
||||||
# we try to conserve the iterable class, if not convert to list
|
# we try to conserve the iterable class, if not convert to list
|
||||||
try:
|
try:
|
||||||
return item.__class__([process_item(val) for val in item])
|
return item.__class__([process_item(val) for val in item])
|
||||||
|
|
@ -678,7 +701,10 @@ def from_pickle(data, db_obj=None):
|
||||||
elif dtype == dict:
|
elif dtype == dict:
|
||||||
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
return dict((process_item(key), process_item(val)) for key, val in item.items())
|
||||||
elif dtype == defaultdict:
|
elif dtype == defaultdict:
|
||||||
return defaultdict(item.default_factory, ((process_item(key), process_item(val)) for key, val in item.items()))
|
return defaultdict(
|
||||||
|
item.default_factory,
|
||||||
|
((process_item(key), process_item(val)) for key, val in item.items()),
|
||||||
|
)
|
||||||
elif dtype == set:
|
elif dtype == set:
|
||||||
return set(process_item(val) for val in item)
|
return set(process_item(val) for val in item)
|
||||||
elif dtype == OrderedDict:
|
elif dtype == OrderedDict:
|
||||||
|
|
@ -692,6 +718,22 @@ def from_pickle(data, db_obj=None):
|
||||||
return item.__class__(process_item(val) for val in item)
|
return item.__class__(process_item(val) for val in item)
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
return [process_item(val) for val in item]
|
return [process_item(val) for val in item]
|
||||||
|
|
||||||
|
if hasattr(item, "__deserialize_dbobjs__"):
|
||||||
|
# this allows the object to custom-deserialize any embedded dbobjs
|
||||||
|
# that we previously serialized with __serialize_dbobjs__.
|
||||||
|
# use the dbunserialize helper in this module.
|
||||||
|
try:
|
||||||
|
item.__deserialize_dbobjs__()
|
||||||
|
except (TypeError, UnpicklingError):
|
||||||
|
# handle recoveries both of classes (requiring classmethods
|
||||||
|
# or instances. Unpickling errors can happen when re-loading the
|
||||||
|
# data from cache (because the hidden entity was already
|
||||||
|
# deserialized and stored back on the object, unpickling it
|
||||||
|
# again fails). TODO: Maybe one could avoid this retry in a
|
||||||
|
# more graceful way?
|
||||||
|
pass
|
||||||
|
|
||||||
return item
|
return item
|
||||||
|
|
||||||
def process_tree(item, parent):
|
def process_tree(item, parent):
|
||||||
|
|
|
||||||
|
|
@ -274,12 +274,13 @@ import inspect
|
||||||
|
|
||||||
from ast import literal_eval
|
from ast import literal_eval
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
from inspect import isfunction, getargspec
|
from inspect import isfunction, getargspec
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from evennia import Command, CmdSet
|
from evennia import Command, CmdSet
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
from evennia.utils.evtable import EvTable
|
from evennia.utils.evtable import EvTable, EvColumn
|
||||||
from evennia.utils.ansi import strip_ansi
|
from evennia.utils.ansi import strip_ansi
|
||||||
from evennia.utils.utils import mod_import, make_iter, pad, to_str, m_len, is_iter, dedent, crop
|
from evennia.utils.utils import mod_import, make_iter, pad, to_str, m_len, is_iter, dedent, crop
|
||||||
from evennia.commands import cmdhandler
|
from evennia.commands import cmdhandler
|
||||||
|
|
@ -1210,7 +1211,6 @@ class EvMenu:
|
||||||
Args:
|
Args:
|
||||||
optionlist (list): List of (key, description) tuples for every
|
optionlist (list): List of (key, description) tuples for every
|
||||||
option related to this node.
|
option related to this node.
|
||||||
caller (Object, Account or None, optional): The caller of the node.
|
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
options (str): The formatted option display.
|
options (str): The formatted option display.
|
||||||
|
|
@ -1229,7 +1229,7 @@ class EvMenu:
|
||||||
table = []
|
table = []
|
||||||
for key, desc in optionlist:
|
for key, desc in optionlist:
|
||||||
if key or desc:
|
if key or desc:
|
||||||
desc_string = ": %s" % desc if desc else ""
|
desc_string = f": {desc}" if desc else ""
|
||||||
table_width_max = max(
|
table_width_max = max(
|
||||||
table_width_max,
|
table_width_max,
|
||||||
max(m_len(p) for p in key.split("\n"))
|
max(m_len(p) for p in key.split("\n"))
|
||||||
|
|
@ -1239,42 +1239,31 @@ class EvMenu:
|
||||||
raw_key = strip_ansi(key)
|
raw_key = strip_ansi(key)
|
||||||
if raw_key != key:
|
if raw_key != key:
|
||||||
# already decorations in key definition
|
# already decorations in key definition
|
||||||
table.append(" |lc%s|lt%s|le%s" % (raw_key, key, desc_string))
|
table.append(f" |lc{raw_key}|lt{key}|le{desc_string}")
|
||||||
else:
|
else:
|
||||||
# add a default white color to key
|
# add a default white color to key
|
||||||
table.append(" |lc%s|lt|w%s|n|le%s" % (raw_key, raw_key, desc_string))
|
table.append(f" |lc{raw_key}|lt|w{key}|n|le{desc_string}")
|
||||||
ncols = _MAX_TEXT_WIDTH // table_width_max # number of ncols
|
ncols = _MAX_TEXT_WIDTH // table_width_max # number of columns
|
||||||
|
|
||||||
if ncols < 0:
|
if ncols < 0:
|
||||||
# no visible option at all
|
# no visible options at all
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
ncols = ncols + 1 if ncols == 0 else ncols
|
ncols = 1 if ncols == 0 else ncols
|
||||||
# get the amount of rows needed (start with 4 rows)
|
|
||||||
nrows = 4
|
|
||||||
while nrows * ncols < nlist:
|
|
||||||
nrows += 1
|
|
||||||
ncols = nlist // nrows # number of full columns
|
|
||||||
nlastcol = nlist % nrows # number of elements in last column
|
|
||||||
|
|
||||||
# get the final column count
|
# minimum number of rows in a column
|
||||||
ncols = ncols + 1 if nlastcol > 0 else ncols
|
min_rows = 4
|
||||||
if ncols > 1:
|
|
||||||
# only extend if longer than one column
|
|
||||||
table.extend([" " for i in range(nrows - nlastcol)])
|
|
||||||
|
|
||||||
# build the actual table grid
|
# split the items into columns
|
||||||
table = [table[icol * nrows : (icol * nrows) + nrows] for icol in range(0, ncols)]
|
split = max(min_rows, ceil(len(table) / ncols))
|
||||||
|
max_end = len(table)
|
||||||
|
cols_list = []
|
||||||
|
for icol in range(ncols):
|
||||||
|
start = icol * split
|
||||||
|
end = min(start + split, max_end)
|
||||||
|
cols_list.append(EvColumn(*table[start:end]))
|
||||||
|
|
||||||
# adjust the width of each column
|
return str(EvTable(table=cols_list, border="none"))
|
||||||
for icol in range(len(table)):
|
|
||||||
col_width = (
|
|
||||||
max(max(m_len(p) for p in part.split("\n")) for part in table[icol]) + colsep
|
|
||||||
)
|
|
||||||
table[icol] = [pad(part, width=col_width + colsep, align="l") for part in table[icol]]
|
|
||||||
|
|
||||||
# format the table into columns
|
|
||||||
return str(EvTable(table=table, border="none"))
|
|
||||||
|
|
||||||
def node_formatter(self, nodetext, optionstext):
|
def node_formatter(self, nodetext, optionstext):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ class EvMore(object):
|
||||||
justify (bool, optional): If set, auto-justify long lines. This must be turned
|
justify (bool, optional): If set, auto-justify long lines. This must be turned
|
||||||
off for fixed-width or formatted output, like tables. It's force-disabled
|
off for fixed-width or formatted output, like tables. It's force-disabled
|
||||||
if `inp` is an EvTable.
|
if `inp` is an EvTable.
|
||||||
justify_kwargs (dict, optional): Keywords for the justifiy function. Used only
|
justify_kwargs (dict, optional): Keywords for the justify function. Used only
|
||||||
if `justify` is True. If this is not set, default arguments will be used.
|
if `justify` is True. If this is not set, default arguments will be used.
|
||||||
exit_on_lastpage (bool, optional): If reaching the last page without the
|
exit_on_lastpage (bool, optional): If reaching the last page without the
|
||||||
page being completely filled, exit pager immediately. If unset,
|
page being completely filled, exit pager immediately. If unset,
|
||||||
|
|
@ -507,7 +507,7 @@ class EvMore(object):
|
||||||
def page_formatter(self, page):
|
def page_formatter(self, page):
|
||||||
"""
|
"""
|
||||||
Page formatter. Every page passes through this method. Override
|
Page formatter. Every page passes through this method. Override
|
||||||
it to customize behvaior per-page. A common use is to generate a new
|
it to customize behavior per-page. A common use is to generate a new
|
||||||
EvTable for every page (this is more efficient than to generate one huge
|
EvTable for every page (this is more efficient than to generate one huge
|
||||||
EvTable across many pages and feed it into EvMore all at once).
|
EvTable across many pages and feed it into EvMore all at once).
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ class _ParsedFunc:
|
||||||
# state storage
|
# state storage
|
||||||
fullstr: str = ""
|
fullstr: str = ""
|
||||||
infuncstr: str = ""
|
infuncstr: str = ""
|
||||||
single_quoted: bool = False
|
single_quoted: int = -1
|
||||||
double_quoted: bool = False
|
double_quoted: int = -1
|
||||||
current_kwarg: str = ""
|
current_kwarg: str = ""
|
||||||
open_lparens: int = 0
|
open_lparens: int = 0
|
||||||
open_lsquate: int = 0
|
open_lsquate: int = 0
|
||||||
|
|
@ -318,8 +318,8 @@ class FuncParser:
|
||||||
# parsing state
|
# parsing state
|
||||||
callstack = []
|
callstack = []
|
||||||
|
|
||||||
single_quoted = False
|
single_quoted = -1
|
||||||
double_quoted = False
|
double_quoted = -1
|
||||||
open_lparens = 0 # open (
|
open_lparens = 0 # open (
|
||||||
open_lsquare = 0 # open [
|
open_lsquare = 0 # open [
|
||||||
open_lcurly = 0 # open {
|
open_lcurly = 0 # open {
|
||||||
|
|
@ -330,6 +330,7 @@ class FuncParser:
|
||||||
curr_func = None
|
curr_func = None
|
||||||
fullstr = "" # final string
|
fullstr = "" # final string
|
||||||
infuncstr = "" # string parts inside the current level of $funcdef (including $)
|
infuncstr = "" # string parts inside the current level of $funcdef (including $)
|
||||||
|
literal_infuncstr = False
|
||||||
|
|
||||||
for char in string:
|
for char in string:
|
||||||
|
|
||||||
|
|
@ -373,12 +374,13 @@ class FuncParser:
|
||||||
curr_func.open_lcurly = open_lcurly
|
curr_func.open_lcurly = open_lcurly
|
||||||
current_kwarg = ""
|
current_kwarg = ""
|
||||||
infuncstr = ""
|
infuncstr = ""
|
||||||
single_quoted = False
|
single_quoted = -1
|
||||||
double_quoted = False
|
double_quoted = -1
|
||||||
open_lparens = 0
|
open_lparens = 0
|
||||||
open_lsquare = 0
|
open_lsquare = 0
|
||||||
open_lcurly = 0
|
open_lcurly = 0
|
||||||
exec_return = ""
|
exec_return = ""
|
||||||
|
literal_infuncstr = False
|
||||||
callstack.append(curr_func)
|
callstack.append(curr_func)
|
||||||
|
|
||||||
# start a new func
|
# start a new func
|
||||||
|
|
@ -401,19 +403,41 @@ class FuncParser:
|
||||||
infuncstr += str(exec_return)
|
infuncstr += str(exec_return)
|
||||||
exec_return = ""
|
exec_return = ""
|
||||||
|
|
||||||
if char == "'": # note that this is the same as "\'"
|
if char == "'" and double_quoted < 0: # note that this is the same as "\'"
|
||||||
# a single quote - flip status
|
# a single quote - flip status
|
||||||
single_quoted = not single_quoted
|
if single_quoted == 0:
|
||||||
infuncstr += char
|
infuncstr = infuncstr[1:]
|
||||||
|
single_quoted = -1
|
||||||
|
elif single_quoted > 0:
|
||||||
|
prefix = infuncstr[0:single_quoted]
|
||||||
|
infuncstr = prefix + infuncstr[single_quoted + 1 :]
|
||||||
|
single_quoted = -1
|
||||||
|
else:
|
||||||
|
infuncstr += char
|
||||||
|
infuncstr = infuncstr.strip()
|
||||||
|
single_quoted = len(infuncstr) - 1
|
||||||
|
literal_infuncstr = True
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if char == '"': # note that this is the same as '\"'
|
if char == '"' and single_quoted < 0: # note that this is the same as '\"'
|
||||||
# a double quote = flip status
|
# a double quote = flip status
|
||||||
double_quoted = not double_quoted
|
if double_quoted == 0:
|
||||||
infuncstr += char
|
infuncstr = infuncstr[1:]
|
||||||
|
double_quoted = -1
|
||||||
|
elif double_quoted > 0:
|
||||||
|
prefix = infuncstr[0:double_quoted]
|
||||||
|
infuncstr = prefix + infuncstr[double_quoted + 1 :]
|
||||||
|
double_quoted = -1
|
||||||
|
else:
|
||||||
|
infuncstr += char
|
||||||
|
infuncstr = infuncstr.strip()
|
||||||
|
double_quoted = len(infuncstr) - 1
|
||||||
|
literal_infuncstr = True
|
||||||
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if double_quoted or single_quoted:
|
if double_quoted >= 0 or single_quoted >= 0:
|
||||||
# inside a string definition - this escapes everything else
|
# inside a string definition - this escapes everything else
|
||||||
infuncstr += char
|
infuncstr += char
|
||||||
continue
|
continue
|
||||||
|
|
@ -477,12 +501,15 @@ class FuncParser:
|
||||||
else:
|
else:
|
||||||
curr_func.args.append(exec_return)
|
curr_func.args.append(exec_return)
|
||||||
else:
|
else:
|
||||||
|
if not literal_infuncstr:
|
||||||
|
infuncstr = infuncstr.strip()
|
||||||
|
|
||||||
# store a string instead
|
# store a string instead
|
||||||
if current_kwarg:
|
if current_kwarg:
|
||||||
curr_func.kwargs[current_kwarg] = infuncstr.strip()
|
curr_func.kwargs[current_kwarg] = infuncstr
|
||||||
elif infuncstr.strip():
|
elif literal_infuncstr or infuncstr.strip():
|
||||||
# don't store the empty string
|
# don't store the empty string
|
||||||
curr_func.args.append(infuncstr.strip())
|
curr_func.args.append(infuncstr)
|
||||||
|
|
||||||
# note that at this point either exec_return or infuncstr will
|
# note that at this point either exec_return or infuncstr will
|
||||||
# be empty. We need to store the full string so we can print
|
# be empty. We need to store the full string so we can print
|
||||||
|
|
@ -493,6 +520,7 @@ class FuncParser:
|
||||||
current_kwarg = ""
|
current_kwarg = ""
|
||||||
exec_return = ""
|
exec_return = ""
|
||||||
infuncstr = ""
|
infuncstr = ""
|
||||||
|
literal_infuncstr = False
|
||||||
|
|
||||||
if char == ")":
|
if char == ")":
|
||||||
# closing the function list - this means we have a
|
# closing the function list - this means we have a
|
||||||
|
|
@ -536,6 +564,7 @@ class FuncParser:
|
||||||
if return_str:
|
if return_str:
|
||||||
exec_return = ""
|
exec_return = ""
|
||||||
infuncstr = ""
|
infuncstr = ""
|
||||||
|
literal_infuncstr = False
|
||||||
continue
|
continue
|
||||||
|
|
||||||
infuncstr += char
|
infuncstr += char
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ class TimeScript(DefaultScript):
|
||||||
callback(*args, **kwargs)
|
callback(*args, **kwargs)
|
||||||
|
|
||||||
seconds = real_seconds_until(**self.db.gametime)
|
seconds = real_seconds_until(**self.db.gametime)
|
||||||
self.restart(interval=seconds)
|
self.start(interval=seconds, force_restart=True)
|
||||||
|
|
||||||
|
|
||||||
# Access functions
|
# Access functions
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ def _log(msg, logfunc, prefix="", **kwargs):
|
||||||
|
|
||||||
# log call functions (each has legacy aliases)
|
# log call functions (each has legacy aliases)
|
||||||
|
|
||||||
|
|
||||||
def log_info(msg, **kwargs):
|
def log_info(msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
Logs any generic debugging/informative info that should appear in the log.
|
Logs any generic debugging/informative info that should appear in the log.
|
||||||
|
|
@ -62,6 +63,7 @@ def log_info(msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
_log(msg, log.info, **kwargs)
|
_log(msg, log.info, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
info = log_info
|
info = log_info
|
||||||
log_infomsg = log_info
|
log_infomsg = log_info
|
||||||
log_msg = log_info
|
log_msg = log_info
|
||||||
|
|
@ -79,6 +81,7 @@ def log_warn(msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
_log(msg, log.warn, **kwargs)
|
_log(msg, log.warn, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
warn = log_warn
|
warn = log_warn
|
||||||
warning = log_warn
|
warning = log_warn
|
||||||
log_warnmsg = log_warn
|
log_warnmsg = log_warn
|
||||||
|
|
@ -120,6 +123,7 @@ def log_trace(msg=None, **kwargs):
|
||||||
if msg:
|
if msg:
|
||||||
_log(msg, log.error, prefix="!!", **kwargs)
|
_log(msg, log.error, prefix="!!", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
log_tracemsg = log_trace
|
log_tracemsg = log_trace
|
||||||
exception = log_trace
|
exception = log_trace
|
||||||
critical = log_trace
|
critical = log_trace
|
||||||
|
|
@ -156,6 +160,7 @@ def log_sec(msg, **kwargs):
|
||||||
"""
|
"""
|
||||||
_log(msg, log.info, prefix="SS", **kwargs)
|
_log(msg, log.info, prefix="SS", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
sec = log_sec
|
sec = log_sec
|
||||||
security = log_sec
|
security = log_sec
|
||||||
log_secmsg = log_sec
|
log_secmsg = log_sec
|
||||||
|
|
@ -174,12 +179,12 @@ def log_server(msg, **kwargs):
|
||||||
_log(msg, log.info, prefix="Server", **kwargs)
|
_log(msg, log.info, prefix="Server", **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GetLogObserver:
|
class GetLogObserver:
|
||||||
"""
|
"""
|
||||||
Sets up how the system logs are formatted.
|
Sets up how the system logs are formatted.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
component_prefix = ""
|
component_prefix = ""
|
||||||
event_levels = {
|
event_levels = {
|
||||||
twisted_logger.LogLevel.debug: "??",
|
twisted_logger.LogLevel.debug: "??",
|
||||||
|
|
@ -207,8 +212,7 @@ class GetLogObserver:
|
||||||
event["log_format"] = str(event.get("log_format", ""))
|
event["log_format"] = str(event.get("log_format", ""))
|
||||||
component_prefix = self.component_prefix or ""
|
component_prefix = self.component_prefix or ""
|
||||||
log_msg = twisted_logger.formatEventAsClassicLogText(
|
log_msg = twisted_logger.formatEventAsClassicLogText(
|
||||||
event,
|
event, formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT)
|
||||||
formatTime=lambda e: twisted_logger.formatTime(e, _TIME_FORMAT)
|
|
||||||
)
|
)
|
||||||
return f"{component_prefix}{log_msg}"
|
return f"{component_prefix}{log_msg}"
|
||||||
|
|
||||||
|
|
@ -218,14 +222,15 @@ class GetLogObserver:
|
||||||
|
|
||||||
# Called by server/portal on startup
|
# Called by server/portal on startup
|
||||||
|
|
||||||
|
|
||||||
class GetPortalLogObserver(GetLogObserver):
|
class GetPortalLogObserver(GetLogObserver):
|
||||||
component_prefix = "|Portal| "
|
component_prefix = "|Portal| "
|
||||||
|
|
||||||
|
|
||||||
class GetServerLogObserver(GetLogObserver):
|
class GetServerLogObserver(GetLogObserver):
|
||||||
component_prefix = ""
|
component_prefix = ""
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# logging overrides
|
# logging overrides
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -352,6 +357,7 @@ class WeeklyLogFile(logfile.DailyLogFile):
|
||||||
self.lastDate = max(self.lastDate, self.toDate())
|
self.lastDate = max(self.lastDate, self.toDate())
|
||||||
self.size += len(data)
|
self.size += len(data)
|
||||||
|
|
||||||
|
|
||||||
# Arbitrary file logger
|
# Arbitrary file logger
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,6 @@ DEFAULT_SETTING_RESETS = dict(
|
||||||
"evennia.game_template.server.conf.prototypefuncs",
|
"evennia.game_template.server.conf.prototypefuncs",
|
||||||
],
|
],
|
||||||
BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest",
|
BASE_GUEST_TYPECLASS="evennia.accounts.accounts.DefaultGuest",
|
||||||
|
|
||||||
# a special setting boolean _TEST_ENVIRONMENT is set by the test runner
|
# a special setting boolean _TEST_ENVIRONMENT is set by the test runner
|
||||||
# while the test suite is running.
|
# while the test suite is running.
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ class TestDbSerialize(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.obj = DefaultObject(
|
self.obj = DefaultObject(db_key="Tester")
|
||||||
db_key="Tester",
|
|
||||||
)
|
|
||||||
self.obj.save()
|
self.obj.save()
|
||||||
|
|
||||||
def test_constants(self):
|
def test_constants(self):
|
||||||
|
|
@ -62,10 +60,12 @@ class TestDbSerialize(TestCase):
|
||||||
self.obj.db.test.sort(key=lambda d: str(d))
|
self.obj.db.test.sort(key=lambda d: str(d))
|
||||||
self.assertEqual(self.obj.db.test, [{0: 1}, {1: 0}])
|
self.assertEqual(self.obj.db.test, [{0: 1}, {1: 0}])
|
||||||
|
|
||||||
def test_dict(self):
|
def test_saverdict(self):
|
||||||
self.obj.db.test = {"a": True}
|
self.obj.db.test = {"a": True}
|
||||||
self.obj.db.test.update({"b": False})
|
self.obj.db.test.update({"b": False})
|
||||||
self.assertEqual(self.obj.db.test, {"a": True, "b": False})
|
self.assertEqual(self.obj.db.test, {"a": True, "b": False})
|
||||||
|
self.obj.db.test |= {"c": 5}
|
||||||
|
self.assertEqual(self.obj.db.test, {"a": True, "b": False, "c": 5})
|
||||||
|
|
||||||
@parameterized.expand(
|
@parameterized.expand(
|
||||||
[
|
[
|
||||||
|
|
@ -88,27 +88,88 @@ class TestDbSerialize(TestCase):
|
||||||
self.assertIsInstance(value, base_type)
|
self.assertIsInstance(value, base_type)
|
||||||
self.assertNotIsInstance(value, saver_type)
|
self.assertNotIsInstance(value, saver_type)
|
||||||
self.assertEqual(value, default_value)
|
self.assertEqual(value, default_value)
|
||||||
self.obj.db.test = {'a': True}
|
self.obj.db.test = {"a": True}
|
||||||
self.obj.db.test.update({'b': False})
|
self.obj.db.test.update({"b": False})
|
||||||
self.assertEqual(self.obj.db.test, {'a': True, 'b': False})
|
self.assertEqual(self.obj.db.test, {"a": True, "b": False})
|
||||||
|
|
||||||
def test_defaultdict(self):
|
def test_defaultdict(self):
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
# baseline behavior for a defaultdict
|
# baseline behavior for a defaultdict
|
||||||
_dd = defaultdict(list)
|
_dd = defaultdict(list)
|
||||||
_dd['a']
|
_dd["a"]
|
||||||
self.assertEqual(_dd, {'a': []})
|
self.assertEqual(_dd, {"a": []})
|
||||||
|
|
||||||
# behavior after defaultdict is set as attribute
|
# behavior after defaultdict is set as attribute
|
||||||
|
|
||||||
dd = defaultdict(list)
|
dd = defaultdict(list)
|
||||||
self.obj.db.test = dd
|
self.obj.db.test = dd
|
||||||
self.obj.db.test['a']
|
self.obj.db.test["a"]
|
||||||
self.assertEqual(self.obj.db.test, {'a': []})
|
self.assertEqual(self.obj.db.test, {"a": []})
|
||||||
|
|
||||||
self.obj.db.test['a'].append(1)
|
self.obj.db.test["a"].append(1)
|
||||||
self.assertEqual(self.obj.db.test, {'a': [1]})
|
self.assertEqual(self.obj.db.test, {"a": [1]})
|
||||||
self.obj.db.test['a'].append(2)
|
self.obj.db.test["a"].append(2)
|
||||||
self.assertEqual(self.obj.db.test, {'a': [1, 2]})
|
self.assertEqual(self.obj.db.test, {"a": [1, 2]})
|
||||||
self.obj.db.test['a'].append(3)
|
self.obj.db.test["a"].append(3)
|
||||||
self.assertEqual(self.obj.db.test, {'a': [1, 2, 3]})
|
self.assertEqual(self.obj.db.test, {"a": [1, 2, 3]})
|
||||||
|
self.obj.db.test |= {"b": [5, 6]}
|
||||||
|
self.assertEqual(self.obj.db.test, {"a": [1, 2, 3], "b": [5, 6]})
|
||||||
|
|
||||||
|
|
||||||
|
class _InvalidContainer:
|
||||||
|
"""Container not saveable in Attribute (if obj is dbobj, it 'hides' it)"""
|
||||||
|
|
||||||
|
def __init__(self, obj):
|
||||||
|
self.hidden_obj = obj
|
||||||
|
|
||||||
|
|
||||||
|
class _ValidContainer(_InvalidContainer):
|
||||||
|
"""Container possible to save in Attribute (handles hidden dbobj explicitly)"""
|
||||||
|
|
||||||
|
def __serialize_dbobjs__(self):
|
||||||
|
self.hidden_obj = dbserialize.dbserialize(self.hidden_obj)
|
||||||
|
|
||||||
|
def __deserialize_dbobjs__(self):
|
||||||
|
self.hidden_obj = dbserialize.dbunserialize(self.hidden_obj)
|
||||||
|
|
||||||
|
|
||||||
|
class DbObjWrappers(TestCase):
|
||||||
|
"""
|
||||||
|
Test the `__serialize_dbobjs__` and `__deserialize_dbobjs__` methods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.dbobj1 = DefaultObject(db_key="Tester1")
|
||||||
|
self.dbobj1.save()
|
||||||
|
self.dbobj2 = DefaultObject(db_key="Tester2")
|
||||||
|
self.dbobj2.save()
|
||||||
|
|
||||||
|
def test_dbobj_hidden_obj__fail(self):
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
self.dbobj1.db.testarg = _InvalidContainer(self.dbobj1)
|
||||||
|
|
||||||
|
def test_consecutive_fetch(self):
|
||||||
|
con = _ValidContainer(self.dbobj2)
|
||||||
|
self.dbobj1.db.testarg = con
|
||||||
|
attrobj = self.dbobj1.attributes.get("testarg", return_obj=True)
|
||||||
|
|
||||||
|
self.assertEqual(attrobj.value, con)
|
||||||
|
self.assertEqual(attrobj.value, con)
|
||||||
|
self.assertEqual(attrobj.value.hidden_obj, self.dbobj2)
|
||||||
|
|
||||||
|
def test_dbobj_hidden_obj__success(self):
|
||||||
|
con = _ValidContainer(self.dbobj2)
|
||||||
|
self.dbobj1.db.testarg = con
|
||||||
|
|
||||||
|
# accessing the same data twice
|
||||||
|
res1 = self.dbobj1.db.testarg
|
||||||
|
res2 = self.dbobj1.db.testarg
|
||||||
|
|
||||||
|
self.assertEqual(res1, res2)
|
||||||
|
self.assertEqual(res1, con)
|
||||||
|
self.assertEqual(res2, con)
|
||||||
|
self.assertEqual(res1.hidden_obj, self.dbobj2)
|
||||||
|
self.assertEqual(res2.hidden_obj, self.dbobj2)
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ def _double_callable(*args, **kwargs):
|
||||||
def _eval_callable(*args, **kwargs):
|
def _eval_callable(*args, **kwargs):
|
||||||
if args:
|
if args:
|
||||||
return simple_eval(args[0])
|
return simple_eval(args[0])
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -113,25 +114,25 @@ class TestFuncParser(TestCase):
|
||||||
("$foo() Test noargs5", "_test() Test noargs5"),
|
("$foo() Test noargs5", "_test() Test noargs5"),
|
||||||
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
|
("Test args1 $foo(a,b,c)", "Test args1 _test(a, b, c)"),
|
||||||
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"),
|
("Test args2 $bar(foo, bar, too)", "Test args2 _test(foo, bar, too)"),
|
||||||
("Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, ' too')"),
|
(r"Test args3 $bar(foo, bar, ' too')", "Test args3 _test(foo, bar, too)"),
|
||||||
("Test args4 $foo('')", "Test args4 _test('')"),
|
("Test args4 $foo('')", "Test args4 _test()"),
|
||||||
('Test args4 $foo("")', 'Test args4 _test("")'),
|
('Test args4 $foo("")', "Test args4 _test()"),
|
||||||
("Test args5 $foo(\(\))", "Test args5 _test(())"),
|
("Test args5 $foo(\(\))", "Test args5 _test(())"),
|
||||||
("Test args6 $foo(\()", "Test args6 _test(()"),
|
("Test args6 $foo(\()", "Test args6 _test(()"),
|
||||||
("Test args7 $foo(())", "Test args7 _test(())"),
|
("Test args7 $foo(())", "Test args7 _test(())"),
|
||||||
("Test args8 $foo())", "Test args8 _test())"),
|
("Test args8 $foo())", "Test args8 _test())"),
|
||||||
("Test args9 $foo(=)", "Test args9 _test(=)"),
|
("Test args9 $foo(=)", "Test args9 _test(=)"),
|
||||||
("Test args10 $foo(\,)", "Test args10 _test(,)"),
|
("Test args10 $foo(\,)", "Test args10 _test(,)"),
|
||||||
("Test args10 $foo(',')", "Test args10 _test(',')"),
|
("Test args10 $foo(',')", "Test args10 _test(,)"),
|
||||||
("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax
|
("Test args11 $foo(()", "Test args11 $foo(()"), # invalid syntax
|
||||||
(
|
(
|
||||||
"Test kwarg1 $bar(foo=1, bar='foo', too=ere)",
|
"Test kwarg1 $bar(foo=1, bar='foo', too=ere)",
|
||||||
"Test kwarg1 _test(foo=1, bar='foo', too=ere)",
|
"Test kwarg1 _test(foo=1, bar=foo, too=ere)",
|
||||||
),
|
),
|
||||||
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"),
|
("Test kwarg2 $bar(foo,bar,too=ere)", "Test kwarg2 _test(foo, bar, too=ere)"),
|
||||||
("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"),
|
("test kwarg3 $foo(foo = bar, bar = ere )", "test kwarg3 _test(foo=bar, bar=ere)"),
|
||||||
(
|
(
|
||||||
"test kwarg4 $foo(foo =' bar ',\" bar \"= ere )",
|
r"test kwarg4 $foo(foo =\' bar \',\" bar \"= ere )",
|
||||||
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
|
"test kwarg4 _test(foo=' bar ', \" bar \"=ere)",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
|
@ -180,22 +181,29 @@ class TestFuncParser(TestCase):
|
||||||
("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"),
|
("Test clr $clr(r, This is a red string!)", "Test clr |rThis is a red string!|n"),
|
||||||
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
|
("Test eval1 $eval(21 + 21 - 10)", "Test eval1 32"),
|
||||||
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
|
("Test eval2 $eval((21 + 21) / 2)", "Test eval2 21.0"),
|
||||||
("Test eval3 $eval('21' + 'foo' + 'bar')", "Test eval3 21foobar"),
|
("Test eval3 $eval(\"'21' + 'foo' + 'bar'\")", "Test eval3 21foobar"),
|
||||||
("Test eval4 $eval('21' + '$repl()' + '' + str(10 // 2))", "Test eval4 21rr5"),
|
(r"Test eval4 $eval(\'21\' + \'$repl()\' + \"''\" + str(10 // 2))", "Test eval4 21rr5"),
|
||||||
("Test eval5 $eval('21' + '\$repl()' + '' + str(10 // 2))", "Test eval5 21$repl()5"),
|
(
|
||||||
("Test eval6 $eval('$repl(a)' + '$repl(b)')", "Test eval6 rarrbr"),
|
r"Test eval5 $eval(\'21\' + \'\$repl()\' + \'\' + str(10 // 2))",
|
||||||
|
"Test eval5 21$repl()5",
|
||||||
|
),
|
||||||
|
("Test eval6 $eval(\"'$repl(a)' + '$repl(b)'\")", "Test eval6 rarrbr"),
|
||||||
("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"),
|
("Test type1 $typ([1,2,3,4])", "Test type1 <class 'list'>"),
|
||||||
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
|
("Test type2 $typ((1,2,3,4))", "Test type2 <class 'tuple'>"),
|
||||||
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
|
("Test type3 $typ({1,2,3,4})", "Test type3 <class 'set'>"),
|
||||||
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
|
("Test type4 $typ({1:2,3:4})", "Test type4 <class 'dict'>"),
|
||||||
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
|
("Test type5 $typ(1), $typ(1.0)", "Test type5 <class 'int'>, <class 'float'>"),
|
||||||
("Test type6 $typ('1'), $typ(\"1.0\")", "Test type6 <class 'str'>, <class 'str'>"),
|
(
|
||||||
|
"Test type6 $typ(\"'1'\"), $typ('\"1.0\"')",
|
||||||
|
"Test type6 <class 'str'>, <class 'str'>",
|
||||||
|
),
|
||||||
("Test add1 $add(1, 2)", "Test add1 3"),
|
("Test add1 $add(1, 2)", "Test add1 3"),
|
||||||
("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"),
|
("Test add2 $add([1,2,3,4], [5,6])", "Test add2 [1, 2, 3, 4, 5, 6]"),
|
||||||
("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"),
|
("Test literal1 $sum($lit([1,2,3,4,5,6]))", "Test literal1 21"),
|
||||||
("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"),
|
("Test literal2 $typ($lit(1))", "Test literal2 <class 'int'>"),
|
||||||
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
|
("Test literal3 $typ($lit(1)aaa)", "Test literal3 <class 'str'>"),
|
||||||
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
|
("Test literal4 $typ(aaa$lit(1))", "Test literal4 <class 'str'>"),
|
||||||
|
("Test spider's thread", "Test spider's thread"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_parse(self, string, expected):
|
def test_parse(self, string, expected):
|
||||||
|
|
@ -258,7 +266,11 @@ class TestFuncParser(TestCase):
|
||||||
self.assertEqual([1, 2, 3, 4], ret)
|
self.assertEqual([1, 2, 3, 4], ret)
|
||||||
self.assertTrue(isinstance(ret, list))
|
self.assertTrue(isinstance(ret, list))
|
||||||
|
|
||||||
ret = self.parser.parse_to_any("$lit('')")
|
ret = self.parser.parse_to_any("$lit(\"''\")")
|
||||||
|
self.assertEqual("", ret)
|
||||||
|
self.assertTrue(isinstance(ret, str))
|
||||||
|
|
||||||
|
ret = self.parser.parse_to_any(r"$lit(\'\')")
|
||||||
self.assertEqual("", ret)
|
self.assertEqual("", ret)
|
||||||
self.assertTrue(isinstance(ret, str))
|
self.assertTrue(isinstance(ret, str))
|
||||||
|
|
||||||
|
|
@ -390,7 +402,8 @@ class TestDefaultCallables(TestCase):
|
||||||
("Some $rjust(Hello, 30)", "Some Hello"),
|
("Some $rjust(Hello, 30)", "Some Hello"),
|
||||||
("Some $rjust(Hello, width=30)", "Some Hello"),
|
("Some $rjust(Hello, width=30)", "Some Hello"),
|
||||||
("Some $cjust(Hello, 30)", "Some Hello "),
|
("Some $cjust(Hello, 30)", "Some Hello "),
|
||||||
("Some $eval('-'*20)Hello", "Some --------------------Hello"),
|
("Some $eval(\"'-'*20\")Hello", "Some --------------------Hello"),
|
||||||
|
('$crop("spider\'s silk", 5)', "spide"),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
def test_other_callables(self, string, expected):
|
def test_other_callables(self, string, expected):
|
||||||
|
|
@ -455,15 +468,16 @@ class TestDefaultCallables(TestCase):
|
||||||
self.parser.parse(
|
self.parser.parse(
|
||||||
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
|
"this should be $pad('''escaped,''' and '''instead,''' cropped $crop(with a long,5) text., 80)"
|
||||||
),
|
),
|
||||||
"this should be '''escaped,''' and '''instead,''' cropped with text. ",
|
"this should be escaped, and instead, cropped with text. ",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_escaped2(self):
|
def test_escaped2(self):
|
||||||
|
raw_str = 'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
|
||||||
|
expected = "this should be escaped, and instead, cropped with text. "
|
||||||
|
result = self.parser.parse(raw_str)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
self.parser.parse(
|
result,
|
||||||
'this should be $pad("""escaped,""" and """instead,""" cropped $crop(with a long,5) text., 80)'
|
expected,
|
||||||
),
|
|
||||||
'this should be """escaped,""" and """instead,""" cropped with text. ',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
50
evennia/utils/tests/test_search.py
Normal file
50
evennia/utils/tests/test_search.py
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
from evennia.scripts.scripts import DefaultScript
|
||||||
|
from evennia.utils.test_resources import EvenniaTest
|
||||||
|
from evennia.utils.search import search_script_attribute, search_script_tag
|
||||||
|
|
||||||
|
class TestSearch(EvenniaTest):
|
||||||
|
|
||||||
|
def test_search_script_tag(self):
|
||||||
|
"""Check that a script can be found by its tag."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.tags.add("a-tag")
|
||||||
|
found = search_script_tag("a-tag")
|
||||||
|
self.assertEqual(len(found), 1, errors)
|
||||||
|
self.assertEqual(script.key, found[0].key, errors)
|
||||||
|
|
||||||
|
def test_search_script_tag_category(self):
|
||||||
|
"""Check that a script can be found by its tag and category."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.tags.add("a-tag", category="a-category")
|
||||||
|
found = search_script_tag("a-tag", category="a-category")
|
||||||
|
self.assertEqual(len(found), 1, errors)
|
||||||
|
self.assertEqual(script.key, found[0].key, errors)
|
||||||
|
|
||||||
|
def test_search_script_tag_wrong_category(self):
|
||||||
|
"""Check that a script cannot be found by the wrong category."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.tags.add("a-tag", category="a-category")
|
||||||
|
found = search_script_tag("a-tag", category="wrong-category")
|
||||||
|
self.assertEqual(len(found), 0, errors)
|
||||||
|
|
||||||
|
def test_search_script_tag_wrong(self):
|
||||||
|
"""Check that a script cannot be found by the wrong tag."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.tags.add("a-tag", category="a-category")
|
||||||
|
found = search_script_tag("wrong-tag", category="a-category")
|
||||||
|
self.assertEqual(len(found), 0, errors)
|
||||||
|
|
||||||
|
def test_search_script_attribute(self):
|
||||||
|
"""Check that a script can be found by its attributes."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.db.an_attribute = "some value"
|
||||||
|
found = search_script_attribute(key="an_attribute", value="some value")
|
||||||
|
self.assertEqual(len(found), 1, errors)
|
||||||
|
self.assertEqual(script.key, found[0].key, errors)
|
||||||
|
|
||||||
|
def test_search_script_attribute_wrong(self):
|
||||||
|
"""Check that a script cannot be found by wrong value of its attributes."""
|
||||||
|
script, errors = DefaultScript.create("a-script")
|
||||||
|
script.db.an_attribute = "some value"
|
||||||
|
found = search_script_attribute(key="an_attribute", value="wrong value")
|
||||||
|
self.assertEqual(len(found), 0, errors)
|
||||||
|
|
@ -7,20 +7,22 @@ import mock
|
||||||
|
|
||||||
|
|
||||||
class TestText2Html(TestCase):
|
class TestText2Html(TestCase):
|
||||||
def test_re_color(self):
|
def test_format_styles(self):
|
||||||
parser = text2html.HTML_PARSER
|
parser = text2html.HTML_PARSER
|
||||||
self.assertEqual("foo", parser.re_color("foo"))
|
self.assertEqual("foo", parser.format_styles("foo"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'<span class="color-001">red</span>foo',
|
'<span class="color-001">red</span>foo',
|
||||||
parser.re_color(ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
parser.format_styles(
|
||||||
|
ansi.ANSI_UNHILITE + ansi.ANSI_RED + "red" + ansi.ANSI_NORMAL + "foo"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'<span class="bgcolor-001">red</span>foo',
|
'<span class="bgcolor-001">red</span>foo',
|
||||||
parser.re_color(ansi.ANSI_BACK_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
parser.format_styles(ansi.ANSI_BACK_RED + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'<span class="bgcolor-001"><span class="color-002">red</span></span>foo',
|
'<span class="bgcolor-001 color-002">red</span>foo',
|
||||||
parser.re_color(
|
parser.format_styles(
|
||||||
ansi.ANSI_BACK_RED
|
ansi.ANSI_BACK_RED
|
||||||
+ ansi.ANSI_UNHILITE
|
+ ansi.ANSI_UNHILITE
|
||||||
+ ansi.ANSI_GREEN
|
+ ansi.ANSI_GREEN
|
||||||
|
|
@ -29,75 +31,25 @@ class TestText2Html(TestCase):
|
||||||
+ "foo"
|
+ "foo"
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_re_bold(self):
|
|
||||||
parser = text2html.HTML_PARSER
|
|
||||||
self.assertEqual("foo", parser.re_bold("foo"))
|
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
# "a <strong>red</strong>foo", # TODO: why not?
|
'a <span class="underline">red</span>foo',
|
||||||
"a <strong>redfoo</strong>",
|
parser.format_styles("a " + ansi.ANSI_UNDERLINE + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||||
parser.re_bold("a " + ansi.ANSI_HILITE + "red" + ansi.ANSI_UNHILITE + "foo"),
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'a <span class="blink">red</span>foo',
|
||||||
|
parser.format_styles("a " + ansi.ANSI_BLINK + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
'a <span class="bgcolor-007 color-000">red</span>foo',
|
||||||
|
parser.format_styles("a " + ansi.ANSI_INVERSE + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||||
)
|
)
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_re_underline(self):
|
|
||||||
parser = text2html.HTML_PARSER
|
|
||||||
self.assertEqual("foo", parser.re_underline("foo"))
|
|
||||||
self.assertEqual(
|
|
||||||
'a <span class="underline">red</span>' + ansi.ANSI_NORMAL + "foo",
|
|
||||||
parser.re_underline(
|
|
||||||
"a "
|
|
||||||
+ ansi.ANSI_UNDERLINE
|
|
||||||
+ "red"
|
|
||||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
|
||||||
+ "foo"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_re_blinking(self):
|
|
||||||
parser = text2html.HTML_PARSER
|
|
||||||
self.assertEqual("foo", parser.re_blinking("foo"))
|
|
||||||
self.assertEqual(
|
|
||||||
'a <span class="blink">red</span>' + ansi.ANSI_NORMAL + "foo",
|
|
||||||
parser.re_blinking(
|
|
||||||
"a "
|
|
||||||
+ ansi.ANSI_BLINK
|
|
||||||
+ "red"
|
|
||||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
|
||||||
+ "foo"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_re_inversing(self):
|
|
||||||
parser = text2html.HTML_PARSER
|
|
||||||
self.assertEqual("foo", parser.re_inversing("foo"))
|
|
||||||
self.assertEqual(
|
|
||||||
'a <span class="inverse">red</span>' + ansi.ANSI_NORMAL + "foo",
|
|
||||||
parser.re_inversing(
|
|
||||||
"a "
|
|
||||||
+ ansi.ANSI_INVERSE
|
|
||||||
+ "red"
|
|
||||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
|
||||||
+ "foo"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_remove_bells(self):
|
def test_remove_bells(self):
|
||||||
parser = text2html.HTML_PARSER
|
parser = text2html.HTML_PARSER
|
||||||
self.assertEqual("foo", parser.remove_bells("foo"))
|
self.assertEqual("foo", parser.remove_bells("foo"))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
"a red" + ansi.ANSI_NORMAL + "foo",
|
"a red" + ansi.ANSI_NORMAL + "foo",
|
||||||
parser.remove_bells(
|
parser.remove_bells("a " + ansi.ANSI_BEEP + "red" + ansi.ANSI_NORMAL + "foo"),
|
||||||
"a "
|
|
||||||
+ ansi.ANSI_BEEP
|
|
||||||
+ "red"
|
|
||||||
+ ansi.ANSI_NORMAL # TODO: why does it keep it?
|
|
||||||
+ "foo"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_remove_backspaces(self):
|
def test_remove_backspaces(self):
|
||||||
|
|
@ -110,7 +62,6 @@ class TestText2Html(TestCase):
|
||||||
self.assertEqual("foo", parser.convert_linebreaks("foo"))
|
self.assertEqual("foo", parser.convert_linebreaks("foo"))
|
||||||
self.assertEqual("a<br> redfoo<br>", parser.convert_linebreaks("a\n redfoo\n"))
|
self.assertEqual("a<br> redfoo<br>", parser.convert_linebreaks("a\n redfoo\n"))
|
||||||
|
|
||||||
@unittest.skip("parser issues")
|
|
||||||
def test_convert_urls(self):
|
def test_convert_urls(self):
|
||||||
parser = text2html.HTML_PARSER
|
parser = text2html.HTML_PARSER
|
||||||
self.assertEqual("foo", parser.convert_urls("foo"))
|
self.assertEqual("foo", parser.convert_urls("foo"))
|
||||||
|
|
@ -118,7 +69,6 @@ class TestText2Html(TestCase):
|
||||||
'a <a href="http://redfoo" target="_blank">http://redfoo</a> runs',
|
'a <a href="http://redfoo" target="_blank">http://redfoo</a> runs',
|
||||||
parser.convert_urls("a http://redfoo runs"),
|
parser.convert_urls("a http://redfoo runs"),
|
||||||
)
|
)
|
||||||
# TODO: doesn't URL encode correctly
|
|
||||||
|
|
||||||
def test_sub_mxp_links(self):
|
def test_sub_mxp_links(self):
|
||||||
parser = text2html.HTML_PARSER
|
parser = text2html.HTML_PARSER
|
||||||
|
|
@ -186,22 +136,22 @@ class TestText2Html(TestCase):
|
||||||
self.assertEqual("foo", text2html.parse_html("foo"))
|
self.assertEqual("foo", text2html.parse_html("foo"))
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
# TODO: note that the blink is currently *not* correctly aborted
|
|
||||||
# with |n here! This is probably not possible to correctly handle
|
|
||||||
# with regex - a stateful parser may be needed.
|
|
||||||
# blink back-cyan normal underline red green yellow blue magenta cyan back-green
|
|
||||||
text2html.parse_html("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"),
|
text2html.parse_html("|^|[CHello|n|u|rW|go|yr|bl|md|c!|[G!"),
|
||||||
'<span class="blink">'
|
'<span class="blink bgcolor-006">'
|
||||||
'<span class="bgcolor-006">Hello</span>' # noqa
|
"Hello"
|
||||||
'<span class="underline">'
|
'</span><span class="underline color-009">'
|
||||||
'<span class="color-009">W</span>' # noqa
|
"W"
|
||||||
'<span class="color-010">o</span>'
|
'</span><span class="underline color-010">'
|
||||||
'<span class="color-011">r</span>'
|
"o"
|
||||||
'<span class="color-012">l</span>'
|
'</span><span class="underline color-011">'
|
||||||
'<span class="color-013">d</span>'
|
"r"
|
||||||
'<span class="color-014">!'
|
'</span><span class="underline color-012">'
|
||||||
'<span class="bgcolor-002">!</span>' # noqa
|
"l"
|
||||||
"</span>"
|
'</span><span class="underline color-013">'
|
||||||
"</span>"
|
"d"
|
||||||
|
'</span><span class="underline color-014">'
|
||||||
|
"!"
|
||||||
|
'</span><span class="underline bgcolor-002 color-014">'
|
||||||
|
"!"
|
||||||
"</span>",
|
"</span>",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,10 @@ import re
|
||||||
from html import escape as html_escape
|
from html import escape as html_escape
|
||||||
from .ansi import *
|
from .ansi import *
|
||||||
|
|
||||||
|
|
||||||
# All xterm256 RGB equivalents
|
# All xterm256 RGB equivalents
|
||||||
|
|
||||||
XTERM256_FG = "\033[38;5;%sm"
|
XTERM256_FG = "\033[38;5;{}m"
|
||||||
XTERM256_BG = "\033[48;5;%sm"
|
XTERM256_BG = "\033[48;5;{}m"
|
||||||
|
|
||||||
|
|
||||||
class TextToHTMLparser(object):
|
class TextToHTMLparser(object):
|
||||||
|
|
@ -25,77 +24,65 @@ class TextToHTMLparser(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tabstop = 4
|
tabstop = 4
|
||||||
# mapping html color name <-> ansi code.
|
|
||||||
hilite = ANSI_HILITE
|
|
||||||
unhilite = ANSI_UNHILITE # this will be stripped - there is no css equivalent.
|
|
||||||
normal = ANSI_NORMAL # "
|
|
||||||
underline = ANSI_UNDERLINE
|
|
||||||
blink = ANSI_BLINK
|
|
||||||
inverse = ANSI_INVERSE # this will produce an outline; no obvious css equivalent?
|
|
||||||
colorcodes = [
|
|
||||||
("color-000", unhilite + ANSI_BLACK), # pure black
|
|
||||||
("color-001", unhilite + ANSI_RED),
|
|
||||||
("color-002", unhilite + ANSI_GREEN),
|
|
||||||
("color-003", unhilite + ANSI_YELLOW),
|
|
||||||
("color-004", unhilite + ANSI_BLUE),
|
|
||||||
("color-005", unhilite + ANSI_MAGENTA),
|
|
||||||
("color-006", unhilite + ANSI_CYAN),
|
|
||||||
("color-007", unhilite + ANSI_WHITE), # light grey
|
|
||||||
("color-008", hilite + ANSI_BLACK), # dark grey
|
|
||||||
("color-009", hilite + ANSI_RED),
|
|
||||||
("color-010", hilite + ANSI_GREEN),
|
|
||||||
("color-011", hilite + ANSI_YELLOW),
|
|
||||||
("color-012", hilite + ANSI_BLUE),
|
|
||||||
("color-013", hilite + ANSI_MAGENTA),
|
|
||||||
("color-014", hilite + ANSI_CYAN),
|
|
||||||
("color-015", hilite + ANSI_WHITE), # pure white
|
|
||||||
] + [("color-%03i" % (i + 16), XTERM256_FG % ("%i" % (i + 16))) for i in range(240)]
|
|
||||||
|
|
||||||
colorback = [
|
style_codes = [
|
||||||
("bgcolor-000", ANSI_BACK_BLACK), # pure black
|
# non-color style markers
|
||||||
("bgcolor-001", ANSI_BACK_RED),
|
ANSI_NORMAL,
|
||||||
("bgcolor-002", ANSI_BACK_GREEN),
|
ANSI_UNDERLINE,
|
||||||
("bgcolor-003", ANSI_BACK_YELLOW),
|
ANSI_HILITE,
|
||||||
("bgcolor-004", ANSI_BACK_BLUE),
|
ANSI_UNHILITE,
|
||||||
("bgcolor-005", ANSI_BACK_MAGENTA),
|
ANSI_INVERSE,
|
||||||
("bgcolor-006", ANSI_BACK_CYAN),
|
ANSI_BLINK,
|
||||||
("bgcolor-007", ANSI_BACK_WHITE), # light grey
|
ANSI_INV_HILITE,
|
||||||
("bgcolor-008", hilite + ANSI_BACK_BLACK), # dark grey
|
ANSI_BLINK_HILITE,
|
||||||
("bgcolor-009", hilite + ANSI_BACK_RED),
|
ANSI_INV_BLINK,
|
||||||
("bgcolor-010", hilite + ANSI_BACK_GREEN),
|
ANSI_INV_BLINK_HILITE,
|
||||||
("bgcolor-011", hilite + ANSI_BACK_YELLOW),
|
]
|
||||||
("bgcolor-012", hilite + ANSI_BACK_BLUE),
|
|
||||||
("bgcolor-013", hilite + ANSI_BACK_MAGENTA),
|
|
||||||
("bgcolor-014", hilite + ANSI_BACK_CYAN),
|
|
||||||
("bgcolor-015", hilite + ANSI_BACK_WHITE), # pure white
|
|
||||||
] + [("bgcolor-%03i" % (i + 16), XTERM256_BG % ("%i" % (i + 16))) for i in range(240)]
|
|
||||||
|
|
||||||
# make sure to escape [
|
ansi_color_codes = [
|
||||||
# colorcodes = [(c, code.replace("[", r"\[")) for c, code in colorcodes]
|
# Foreground colors
|
||||||
# colorback = [(c, code.replace("[", r"\[")) for c, code in colorback]
|
ANSI_BLACK,
|
||||||
fg_colormap = dict((code, clr) for clr, code in colorcodes)
|
ANSI_RED,
|
||||||
bg_colormap = dict((code, clr) for clr, code in colorback)
|
ANSI_GREEN,
|
||||||
|
ANSI_YELLOW,
|
||||||
|
ANSI_BLUE,
|
||||||
|
ANSI_MAGENTA,
|
||||||
|
ANSI_CYAN,
|
||||||
|
ANSI_WHITE,
|
||||||
|
]
|
||||||
|
|
||||||
# create stop markers
|
xterm_fg_codes = [XTERM256_FG.format(i + 16) for i in range(240)]
|
||||||
fgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m|\033\[0m|$"
|
|
||||||
bgstop = "(?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m|\033\[0m|$"
|
|
||||||
bgfgstop = bgstop[:-2] + fgstop
|
|
||||||
|
|
||||||
fgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[3[0-8].*?m)"
|
ansi_bg_codes = [
|
||||||
bgstart = "((?:\033\[1m|\033\[22m){0,1}\033\[4[0-8].*?m)"
|
# Background colors
|
||||||
bgfgstart = bgstart + r"((?:\033\[1m|\033\[22m){0,1}\033\[[3-4][0-8].*?m){0,1}"
|
ANSI_BACK_BLACK,
|
||||||
|
ANSI_BACK_RED,
|
||||||
|
ANSI_BACK_GREEN,
|
||||||
|
ANSI_BACK_YELLOW,
|
||||||
|
ANSI_BACK_BLUE,
|
||||||
|
ANSI_BACK_MAGENTA,
|
||||||
|
ANSI_BACK_CYAN,
|
||||||
|
ANSI_BACK_WHITE,
|
||||||
|
]
|
||||||
|
|
||||||
# extract color markers, tagging the start marker and the text marked
|
xterm_bg_codes = [XTERM256_BG.format(i + 16) for i in range(240)]
|
||||||
re_fgs = re.compile(fgstart + "(.*?)(?=" + fgstop + ")")
|
|
||||||
re_bgs = re.compile(bgstart + "(.*?)(?=" + bgstop + ")")
|
re_style = re.compile(
|
||||||
re_bgfg = re.compile(bgfgstart + "(.*?)(?=" + bgfgstop + ")")
|
r"({})".format(
|
||||||
|
"|".join(
|
||||||
|
style_codes + ansi_color_codes + xterm_fg_codes + ansi_bg_codes + xterm_bg_codes
|
||||||
|
).replace("[", r"\[")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
colorlist = (
|
||||||
|
[ANSI_UNHILITE + code for code in ansi_color_codes]
|
||||||
|
+ [ANSI_HILITE + code for code in ansi_color_codes]
|
||||||
|
+ xterm_fg_codes
|
||||||
|
)
|
||||||
|
|
||||||
|
bglist = ansi_bg_codes + [ANSI_HILITE + code for code in ansi_bg_codes] + xterm_bg_codes
|
||||||
|
|
||||||
re_normal = re.compile(normal.replace("[", r"\["))
|
|
||||||
re_hilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (hilite.replace("[", r"\["), fgstop, bgstop))
|
|
||||||
re_unhilite = re.compile("(?:%s)(.*)(?=%s|%s)" % (unhilite.replace("[", r"\["), fgstop, bgstop))
|
|
||||||
re_uline = re.compile("(?:%s)(.*?)(?=%s|%s)" % (underline.replace("[", r"\["), fgstop, bgstop))
|
|
||||||
re_blink = re.compile("(?:%s)(.*?)(?=%s|%s)" % (blink.replace("[", r"\["), fgstop, bgstop))
|
|
||||||
re_inverse = re.compile("(?:%s)(.*?)(?=%s|%s)" % (inverse.replace("[", r"\["), fgstop, bgstop))
|
|
||||||
re_string = re.compile(
|
re_string = re.compile(
|
||||||
r"(?P<htmlchars>[<&>])|(?P<tab>[\t]+)|(?P<lineend>\r\n|\r|\n)",
|
r"(?P<htmlchars>[<&>])|(?P<tab>[\t]+)|(?P<lineend>\r\n|\r|\n)",
|
||||||
re.S | re.M | re.I,
|
re.S | re.M | re.I,
|
||||||
|
|
@ -106,100 +93,6 @@ class TextToHTMLparser(object):
|
||||||
re_mxplink = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
re_mxplink = re.compile(r"\|lc(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||||
re_mxpurl = re.compile(r"\|lu(.*?)\|lt(.*?)\|le", re.DOTALL)
|
re_mxpurl = re.compile(r"\|lu(.*?)\|lt(.*?)\|le", re.DOTALL)
|
||||||
|
|
||||||
def _sub_bgfg(self, colormatch):
|
|
||||||
# print("colormatch.groups()", colormatch.groups())
|
|
||||||
bgcode, fgcode, text = colormatch.groups()
|
|
||||||
if not fgcode:
|
|
||||||
ret = r"""<span class="%s">%s</span>""" % (
|
|
||||||
self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")),
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
ret = r"""<span class="%s"><span class="%s">%s</span></span>""" % (
|
|
||||||
self.bg_colormap.get(bgcode, self.fg_colormap.get(bgcode, "err")),
|
|
||||||
self.fg_colormap.get(fgcode, self.bg_colormap.get(fgcode, "err")),
|
|
||||||
text,
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def _sub_fg(self, colormatch):
|
|
||||||
code, text = colormatch.groups()
|
|
||||||
return r"""<span class="%s">%s</span>""" % (self.fg_colormap.get(code, "err"), text)
|
|
||||||
|
|
||||||
def _sub_bg(self, colormatch):
|
|
||||||
code, text = colormatch.groups()
|
|
||||||
return r"""<span class="%s">%s</span>""" % (self.bg_colormap.get(code, "err"), text)
|
|
||||||
|
|
||||||
def re_color(self, text):
|
|
||||||
"""
|
|
||||||
Replace ansi colors with html color class names. Let the
|
|
||||||
client choose how it will display colors, if it wishes to.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): the string with color to replace.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
text (str): Re-colored text.
|
|
||||||
|
|
||||||
"""
|
|
||||||
text = self.re_bgfg.sub(self._sub_bgfg, text)
|
|
||||||
text = self.re_fgs.sub(self._sub_fg, text)
|
|
||||||
text = self.re_bgs.sub(self._sub_bg, text)
|
|
||||||
text = self.re_normal.sub("", text)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def re_bold(self, text):
|
|
||||||
"""
|
|
||||||
Clean out superfluous hilights rather than set <strong>to make
|
|
||||||
it match the look of telnet.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Text to process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
text (str): Processed text.
|
|
||||||
|
|
||||||
"""
|
|
||||||
text = self.re_hilite.sub(r"<strong>\1</strong>", text)
|
|
||||||
return self.re_unhilite.sub(r"\1", text) # strip unhilite - there is no equivalent in css.
|
|
||||||
|
|
||||||
def re_underline(self, text):
|
|
||||||
"""
|
|
||||||
Replace ansi underline with html underline class name.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Text to process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
text (str): Processed text.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return self.re_uline.sub(r'<span class="underline">\1</span>', text)
|
|
||||||
|
|
||||||
def re_blinking(self, text):
|
|
||||||
"""
|
|
||||||
Replace ansi blink with custom blink css class
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Text to process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
text (str): Processed text.
|
|
||||||
"""
|
|
||||||
return self.re_blink.sub(r'<span class="blink">\1</span>', text)
|
|
||||||
|
|
||||||
def re_inversing(self, text):
|
|
||||||
"""
|
|
||||||
Replace ansi inverse with custom inverse css class
|
|
||||||
|
|
||||||
Args:
|
|
||||||
text (str): Text to process.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
text (str): Processed text.
|
|
||||||
"""
|
|
||||||
return self.re_inverse.sub(r'<span class="inverse">\1</span>', text)
|
|
||||||
|
|
||||||
def remove_bells(self, text):
|
def remove_bells(self, text):
|
||||||
"""
|
"""
|
||||||
Remove ansi specials
|
Remove ansi specials
|
||||||
|
|
@ -211,7 +104,7 @@ class TextToHTMLparser(object):
|
||||||
text (str): Processed text.
|
text (str): Processed text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return text.replace("\07", "")
|
return text.replace(ANSI_BEEP, "")
|
||||||
|
|
||||||
def remove_backspaces(self, text):
|
def remove_backspaces(self, text):
|
||||||
"""
|
"""
|
||||||
|
|
@ -315,6 +208,128 @@ class TextToHTMLparser(object):
|
||||||
return text
|
return text
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def format_styles(self, text):
|
||||||
|
"""
|
||||||
|
Takes a string with parsed ANSI codes and replaces them with
|
||||||
|
HTML spans and CSS classes.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text (str): The string to process.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
text (str): Processed text.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# split out the ANSI codes and clean out any empty items
|
||||||
|
str_list = [substr for substr in self.re_style.split(text) if substr]
|
||||||
|
# initialize all the flags and classes
|
||||||
|
classes = []
|
||||||
|
clean = True
|
||||||
|
inverse = False
|
||||||
|
# default color is light grey - unhilite + white
|
||||||
|
hilight = ANSI_UNHILITE
|
||||||
|
fg = ANSI_WHITE
|
||||||
|
# default bg is black
|
||||||
|
bg = ANSI_BACK_BLACK
|
||||||
|
|
||||||
|
for i, substr in enumerate(str_list):
|
||||||
|
# reset all current styling
|
||||||
|
if substr == ANSI_NORMAL:
|
||||||
|
# close any existing span if necessary
|
||||||
|
str_list[i] = "</span>" if not clean else ""
|
||||||
|
# reset to defaults
|
||||||
|
classes = []
|
||||||
|
clean = True
|
||||||
|
inverse = False
|
||||||
|
hilight = ANSI_UNHILITE
|
||||||
|
fg = ANSI_WHITE
|
||||||
|
bg = ANSI_BACK_BLACK
|
||||||
|
|
||||||
|
# change color
|
||||||
|
elif substr in self.ansi_color_codes + self.xterm_fg_codes:
|
||||||
|
# erase ANSI code from output
|
||||||
|
str_list[i] = ""
|
||||||
|
# set new color
|
||||||
|
fg = substr
|
||||||
|
|
||||||
|
# change bg color
|
||||||
|
elif substr in self.ansi_bg_codes + self.xterm_bg_codes:
|
||||||
|
# erase ANSI code from output
|
||||||
|
str_list[i] = ""
|
||||||
|
# set new bg
|
||||||
|
bg = substr
|
||||||
|
|
||||||
|
# non-color codes
|
||||||
|
elif substr in self.style_codes:
|
||||||
|
# erase ANSI code from output
|
||||||
|
str_list[i] = ""
|
||||||
|
|
||||||
|
# hilight codes
|
||||||
|
if substr in (ANSI_HILITE, ANSI_UNHILITE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE):
|
||||||
|
# set new hilight status
|
||||||
|
hilight = ANSI_UNHILITE if substr == ANSI_UNHILITE else ANSI_HILITE
|
||||||
|
|
||||||
|
# inversion codes
|
||||||
|
if substr in (ANSI_INVERSE, ANSI_INV_HILITE, ANSI_INV_BLINK_HILITE):
|
||||||
|
inverse = True
|
||||||
|
|
||||||
|
# blink codes
|
||||||
|
if (
|
||||||
|
substr in (ANSI_BLINK, ANSI_BLINK_HILITE, ANSI_INV_BLINK_HILITE)
|
||||||
|
and "blink" not in classes
|
||||||
|
):
|
||||||
|
classes.append("blink")
|
||||||
|
|
||||||
|
# underline
|
||||||
|
if substr == ANSI_UNDERLINE and "underline" not in classes:
|
||||||
|
classes.append("underline")
|
||||||
|
|
||||||
|
else:
|
||||||
|
# normal text, add text back to list
|
||||||
|
if not str_list[i - 1]:
|
||||||
|
# prior entry was cleared, which means style change
|
||||||
|
# get indices for the fg and bg codes
|
||||||
|
bg_index = self.bglist.index(bg)
|
||||||
|
try:
|
||||||
|
color_index = self.colorlist.index(hilight + fg)
|
||||||
|
except ValueError:
|
||||||
|
# xterm256 colors don't have the hilight codes
|
||||||
|
color_index = self.colorlist.index(fg)
|
||||||
|
|
||||||
|
if inverse:
|
||||||
|
# inverse means swap fg and bg indices
|
||||||
|
bg_class = "bgcolor-{}".format(str(color_index).rjust(3, "0"))
|
||||||
|
color_class = "color-{}".format(str(bg_index).rjust(3, "0"))
|
||||||
|
else:
|
||||||
|
# use fg and bg indices for classes
|
||||||
|
bg_class = "bgcolor-{}".format(str(bg_index).rjust(3, "0"))
|
||||||
|
color_class = "color-{}".format(str(color_index).rjust(3, "0"))
|
||||||
|
|
||||||
|
# black bg is the default, don't explicitly style
|
||||||
|
if bg_class != "bgcolor-000":
|
||||||
|
classes.append(bg_class)
|
||||||
|
# light grey text is the default, don't explicitly style
|
||||||
|
if color_class != "color-007":
|
||||||
|
classes.append(color_class)
|
||||||
|
# define the new style span
|
||||||
|
prefix = '<span class="{}">'.format(" ".join(classes))
|
||||||
|
# close any prior span
|
||||||
|
if not clean:
|
||||||
|
prefix = "</span>" + prefix
|
||||||
|
# add span to output
|
||||||
|
str_list[i - 1] = prefix
|
||||||
|
|
||||||
|
# clean out color classes to easily update next time
|
||||||
|
classes = [cls for cls in classes if "color" not in cls]
|
||||||
|
# flag as currently being styled
|
||||||
|
clean = False
|
||||||
|
|
||||||
|
# close span if necessary
|
||||||
|
if not clean:
|
||||||
|
str_list.append("</span>")
|
||||||
|
# recombine back into string
|
||||||
|
return "".join(str_list)
|
||||||
|
|
||||||
def parse(self, text, strip_ansi=False):
|
def parse(self, text, strip_ansi=False):
|
||||||
"""
|
"""
|
||||||
Main access function, converts a text containing ANSI codes
|
Main access function, converts a text containing ANSI codes
|
||||||
|
|
@ -328,19 +343,14 @@ class TextToHTMLparser(object):
|
||||||
text (str): Parsed text.
|
text (str): Parsed text.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# print(f"incoming text:\n{text}")
|
|
||||||
# parse everything to ansi first
|
# parse everything to ansi first
|
||||||
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True)
|
text = parse_ansi(text, strip_ansi=strip_ansi, xterm256=True, mxp=True)
|
||||||
# convert all ansi to html
|
# convert all ansi to html
|
||||||
result = re.sub(self.re_string, self.sub_text, text)
|
result = re.sub(self.re_string, self.sub_text, text)
|
||||||
result = re.sub(self.re_mxplink, self.sub_mxp_links, result)
|
result = re.sub(self.re_mxplink, self.sub_mxp_links, result)
|
||||||
result = re.sub(self.re_mxpurl, self.sub_mxp_urls, result)
|
result = re.sub(self.re_mxpurl, self.sub_mxp_urls, result)
|
||||||
result = self.re_color(result)
|
|
||||||
result = self.re_bold(result)
|
|
||||||
result = self.re_underline(result)
|
|
||||||
result = self.re_blinking(result)
|
|
||||||
result = self.re_inversing(result)
|
|
||||||
result = self.remove_bells(result)
|
result = self.remove_bells(result)
|
||||||
|
result = self.format_styles(result)
|
||||||
result = self.convert_linebreaks(result)
|
result = self.convert_linebreaks(result)
|
||||||
result = self.remove_backspaces(result)
|
result = self.remove_backspaces(result)
|
||||||
result = self.convert_urls(result)
|
result = self.convert_urls(result)
|
||||||
|
|
|
||||||
|
|
@ -819,7 +819,7 @@ def latinify(string, default="?", pure_ascii=False):
|
||||||
This is used as a last resort when normal encoding does not work.
|
This is used as a last resort when normal encoding does not work.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
string (str): A string to convert to 'safe characters' convertable
|
string (str): A string to convert to 'safe characters' convertible
|
||||||
to an latin-1 bytestring later.
|
to an latin-1 bytestring later.
|
||||||
default (str, optional): Characters resisting mapping will be replaced
|
default (str, optional): Characters resisting mapping will be replaced
|
||||||
with this character or string. The intent is to apply an encode operation
|
with this character or string. The intent is to apply an encode operation
|
||||||
|
|
@ -1078,7 +1078,7 @@ def delay(timedelay, callback, *args, **kwargs):
|
||||||
Keep in mind that persistent tasks arguments and callback should not
|
Keep in mind that persistent tasks arguments and callback should not
|
||||||
use memory references.
|
use memory references.
|
||||||
If persistent is set to True the delay function will return an int
|
If persistent is set to True the delay function will return an int
|
||||||
which is the task's id itended for use with TASK_HANDLER's do_task
|
which is the task's id intended for use with TASK_HANDLER's do_task
|
||||||
and remove methods.
|
and remove methods.
|
||||||
All persistent tasks whose time delays have passed will be called on server startup.
|
All persistent tasks whose time delays have passed will be called on server startup.
|
||||||
|
|
||||||
|
|
@ -1531,12 +1531,12 @@ def class_from_module(path, defaultpaths=None, fallback=None):
|
||||||
defaultpaths (iterable, optional): If a direct import from `path` fails,
|
defaultpaths (iterable, optional): If a direct import from `path` fails,
|
||||||
try subsequent imports by prepending those paths to `path`.
|
try subsequent imports by prepending those paths to `path`.
|
||||||
fallback (str): If all other attempts fail, use this path as a fallback.
|
fallback (str): If all other attempts fail, use this path as a fallback.
|
||||||
This is intended as a last-resport. In the example of Evennia
|
This is intended as a last-resort. In the example of Evennia
|
||||||
loading, this would be a path to a default parent class in the
|
loading, this would be a path to a default parent class in the
|
||||||
evennia repo itself.
|
evennia repo itself.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
class (Class): An uninstatiated class recovered from path.
|
class (Class): An uninstantiated class recovered from path.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
ImportError: If all loading failed.
|
ImportError: If all loading failed.
|
||||||
|
|
@ -1675,7 +1675,7 @@ def string_partial_matching(alternatives, inp, ret_index=True):
|
||||||
Matching is made from the start of each subword in each
|
Matching is made from the start of each subword in each
|
||||||
alternative. Case is not important. So e.g. "bi sh sw" or just
|
alternative. Case is not important. So e.g. "bi sh sw" or just
|
||||||
"big" or "shiny" or "sw" will match "Big shiny sword". Scoring is
|
"big" or "shiny" or "sw" will match "Big shiny sword". Scoring is
|
||||||
done to allow to separate by most common demoninator. You will get
|
done to allow to separate by most common denominator. You will get
|
||||||
multiple matches returned if appropriate.
|
multiple matches returned if appropriate.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|
@ -1749,7 +1749,7 @@ def format_table(table, extra_space=1):
|
||||||
|
|
||||||
ftable = format_table([[1,2,3], [4,5,6]])
|
ftable = format_table([[1,2,3], [4,5,6]])
|
||||||
string = ""
|
string = ""
|
||||||
for ir, row in enumarate(ftable):
|
for ir, row in enumerate(ftable):
|
||||||
if ir == 0:
|
if ir == 0:
|
||||||
# make first row white
|
# make first row white
|
||||||
string += "\\n|w" + "".join(row) + "|n"
|
string += "\\n|w" + "".join(row) + "|n"
|
||||||
|
|
@ -2695,6 +2695,7 @@ def copy_word_case(base_word, new_word):
|
||||||
+ excess
|
+ excess
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def run_in_main_thread(function_or_method, *args, **kwargs):
|
def run_in_main_thread(function_or_method, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Force a callable to execute in the main Evennia thread. This is only relevant when
|
Force a callable to execute in the main Evennia thread. This is only relevant when
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ An "emitter" object must have a function
|
||||||
// kwargs (obj): keyword-args to listener
|
// kwargs (obj): keyword-args to listener
|
||||||
//
|
//
|
||||||
emit: function (cmdname, args, kwargs) {
|
emit: function (cmdname, args, kwargs) {
|
||||||
if (kwargs.cmdid) {
|
if (kwargs.cmdid && (kwargs.cmdid in cmdmap)) {
|
||||||
cmdmap[kwargs.cmdid].apply(this, [args, kwargs]);
|
cmdmap[kwargs.cmdid].apply(this, [args, kwargs]);
|
||||||
delete cmdmap[kwargs.cmdid];
|
delete cmdmap[kwargs.cmdid];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ autobahn >= 20.7.1, < 21.0.0
|
||||||
lunr == 0.6.0
|
lunr == 0.6.0
|
||||||
simpleeval <= 1.0
|
simpleeval <= 1.0
|
||||||
uritemplate == 4.1.1
|
uritemplate == 4.1.1
|
||||||
|
Jinja2 < 3.1
|
||||||
|
|
||||||
# try to resolve dependency issue in py3.7
|
# try to resolve dependency issue in py3.7
|
||||||
attrs >= 19.2.0
|
attrs >= 19.2.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue