• Stars
    star
    392
  • Rank 109,195 (Top 3 %)
  • Language
    Python
  • License
    Other
  • Created about 2 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A human-readable regular expression module for Python.

Humre

A human-readable regular expression module for Python. Humre handles regex syntax for you and creates regex strings to pass to Python's re.compile(). Pronounced "hum, ree".

It is similar to Swift's regex DSL or an advanced form of Python regex's re.VERBOSE mode. Code is read far more often than it is written, so the verbose Humre code may take a few seconds longer to write but pays for itself by being much easier to read and understand.

Note that until version 1.0 is released, the API for this module could change. Please send suggestions and feedback to [email protected].

Quickstart

>>> from humre import *
>>> regexStr = either(OPEN_PAREN + exactly(3, DIGIT) + CLOSE_PAREN, exactly(3, DIGIT)) + '-' + exactly(3, DIGIT) + '-' + exactly(4, DIGIT)
>>> regexStr
'\\(\\d{3}\\)|\\d{3}-\\d{3}-\\d{4}'
>>> patternObj = compile(regexStr)
>>> matchObj = patternObj.search('Call 415-555-1234 today!')
>>> matchObj
<re.Match object; span=(5, 17), match='415-555-1234'>
>>> matchObj.group()
'415-555-1234'

Frequently Asked Questions

What Does Humre Provide?

Humre provides a collection of functions and constants to create regex strings without having to know the specific regex symbols. These offer more structure and readability than regex strings.

Do I Need to Know Regular Expressions to Use Humre?

Yes and no. You still need to know what regular expressions are and how they're used. But instead of memorizing the punctuation marks for each regex feature you want to use, you can use the easier-to-remember Humre functions and constants. For example, zero_or_more(chars('a-z')) is easier to remember than [a-z]* if you are inexperienced with regex. But I recommend reading a general regex tutorial before using Humre.

What's Wrong with Python's re Module?

It's more what's wrong with regular expression syntax. Regex strings can look like a cryptic mess of punctuation marks, and even if you're an experienced software engineer, complex regex strings can be hard to read and debug.

Doesn't Verbose Mode Fix That Problem?

A little. But because verbose mode still has the regex string as a string value, dev tools such as linters, syntax highlighting, and matching parentheses highlighting can't be employed. Also, dealing with escape characters can still be a pain.

Is Humre a New Reimplementation of Python's re Module?

No. Humre only creates the regex strings to pass to re.compile() (or to pass to humre.compile() which wraps it.)

What Are Benefits of Using Humre Instead of Writing My Own Regex Strings?

  • Your editor's parentheses matching works.
  • Your editor's syntax highlight works.
  • Your editor's linter and type hints tool picks up typos.
  • Your editor's autocomplete works.
  • Auto-formatter tools like Black can automatically format your regex code.
  • Humre handles raw strings/string escaping for you.
  • You can put actual Python comments alongside your Humre code.
  • Better error messages for invalid regexes.

Is It A Good Idea To Use The from humre import * Importing Syntax?

In this case, sure. Generally this form importing is frowned on, but it'll keep your optional(group('cat' + DIGIT)) code from becoming humre.optional(humre.group('cat' + humre.DIGIT)). Note that this will overwrite the (infrequently-used) built-in compile() function with humre.compile(), which is a wrapper for re.compile().

How Do I Combine Humre's Functions and Constants Together?

Every Humre function returns a regex string and every Humre constant is a string, so you can use f-strings and string concatenation to combine them:

>>> from humre import *

>>> exactly(5, DIGIT) + optional(WHITESPACE) + one_or_more(NONWHITESPACE)
'\\d{5}\\s?\\S+'

>>> 'I am looking for %s grapes.' % (exactly(2, DIGIT))
'I am looking for \\d{2} grapes.'

>>> f'I am looking for {exactly(2, DIGIT)} grapes.''
'I am looking for \\d{2} grapes.'

Humre Seems Nice for Beginners, But Why Would Experienced Devs Want to Use It?

TODO

Why Is the humre.compile() Function Not Working When I Pass Flags to It?

Most Humre functions combine their arguments into one string for ease of use (that is, group('cat', 'dog') is the same as group('catdog')). The humre.compile() function does this to, so if you want to pass flags such as

Isn't Using Humre a Performance Hit Compared to Using re?

No. Humre functions are simple functions that do basic string manipulation. You only need to call them once when you create the regex pattern object. Your program, whether large or small, will spend far more time doing the actual pattern matching than creating the regex string.

Most Regexes Are Short Enough That the Syntax Doesn't Get In the Way. Why Use Humre for These?

Sure, the phone number example is simple enough that anyone who knows regex syntax can understand it.

Humre vs re Comparison

Here's a comparison of the code for Python's re module versus the equivalent code with Humre (formatted with the Black code-formatting tool.)

American Phone Number with re:

import re
re.compile('\d{3}-\d{3}-\d{4}')

American Phone Number with Humre:

from humre import *
compile(exactly(3, DIGIT), "-", exactly(3, DIGIT), "-", exactly(4, DIGIT))

Hexadecimal Number with Optional Leading 0x or 0X and Consistent Casing with re:

import re
re.compile('(?:(?:0x|0X)[0-9a-f]+)|(?:(?:0x|0X)[0-9A-F]+)|(?:[0-9a-f]+)|(?:[0-9A-F]+)')

Hexadecimal Number with Optional Leading 0x or 0X and Consistent Casing with re:

from humre import *
compile(
    either(
        noncap_group(noncap_group(either('0x', '0X')), one_or_more(chars('0-9a-f'))),
        noncap_group(noncap_group(either('0x', '0X')), one_or_more(chars('0-9A-F'))),
        noncap_group(one_or_more(chars('0-9a-f'))),
        noncap_group(one_or_more(chars('0-9A-F')))
    )
)

Number with or without comma-formatting including decimal point with re:

import re
re.compile(r'(?:\+|-)?(?:(?:\d{1,3}(?:,\d{3})+)|\d+)(?:\.\d+)?')

Number with or without comma-formatting including decimal point with Humre:

from humre import *
compile(
    # optional negative or positive sign:
    optional(noncap_group(either(PLUS_SIGN, '-'))),
    # whole number section:
    noncap_group(either(
        # number with commas:
        noncap_group(between(1, 3, DIGIT), one_or_more(noncap_group(',', exactly(3, DIGIT)))),
        # number without commas:
        one_or_more(DIGIT)
    )),
    # fractional number section (optional)
    optional(noncap_group(PERIOD, one_or_more(DIGIT)))
    )

Or you can use Humre's included NUMBER pattern:

from humre import *
compile(NUMBER)

Quick Reference

Here's a quick list of all of Humre's functions and constants, and the regex strings that they produce:

Function Regex Equivalent
group('A') '(A)'
optional('A') 'A?'
either('A', 'B', 'C') 'A|B|C'
exactly(3, 'A') 'A{3}'
between(3, 5, 'A') 'A{3:5}'
at_least(3, 'A') 'A{3,}'
at_most(3, 'A') 'A{,3})'
chars('A-Z') '[A-Z]'
nonchars('A-Z') '[^A-Z]'
zero_or_more('A') 'A*'
zero_or_more_lazy('A') 'A*?'
one_or_more('A') 'A+'
one_or_more_lazy('A') 'A+?'
starts_with('A') '^A'
ends_with('A') 'A$'
starts_and_ends_with('A') '^A$'
named_group('group_name', 'A') '(?P<group_name>A)'
noncap_group('A') '(?:A)'
positive_lookahead('A') '(?=A)'
negative_lookahead('A') '(?!A)'
positive_lookbehind('A') '(?<=A)'
negative_lookbehind('A') '(?<!A)'
back_reference(1) r'\1'
back_ref(1) r'\1'
atomic_group('A') '(?>A)'
zero_or_more_possessive('A') 'A*+'
one_or_more_possessive('A') 'A++'
optional_possessive('A') 'A?+'

The convenience group functions combine a Humre function with the group() (or noncap_group()) function since putting regexes in groups is so common, such as with ([A-Z])+ putting the character class

Convenience Function Function Equivalent Regex Equivalent
optional_group('A') optional(group('A')) '(A)?'
optional_noncap_group('A') optional(noncap_group('A')) '(?:A)?'
group_either('A') group(either('A', 'B', 'C')) '(A|B|C)'
noncap_group_either('A') noncap_group(either('A', 'B', 'C')) '(?:A|B|C)'
group_exactly('A') group(exactly(3, 'A')) '(A){3}'
noncap_group_exactly('A') noncap_group(exactly(3, 'A')) '(?:A){3}'
group_between('A') group(between(3, 5, 'A')) '(A){3,5}'
noncap_group_between('A') noncap_group(between(3, 5, 'A')) '(?:A){3,5}'
group_at_least('A') group(at_least(3, 'A')) '(A){3,}'
noncap_group_at_least('A') noncap_group(at_least(3, 'A')) '(?:A){3,}'
group_at_most('A') group(at_most(3, 'A')) '(A){,3}'
noncap_group_at_most('A') noncap_group(at_most(3, 'A')) '(?:A){,3}'
zero_or_more_group('A') zero_or_more(group('A')) '(A)*'
zero_or_more_noncap_group('A') zero_or_more(noncap_group('A')) '(?:A)*'
zero_or_more_lazy_group('A') zero_or_more_lazy(group('A')) '(A)*?'
zero_or_more_lazy_noncap_group('A') zero_or_more_lazy(noncap_group('A')) '(?:A)*?'
one_or_more_group('A') one_or_more(group('A')) '(A)+'
one_or_more_noncap_group('A') one_or_more(noncap_group('A')) '(?:A)+'
one_or_more_lazy_group('A') one_or_more_lazy(group('A')) '(A)+?'
one_or_more_lazy_noncap_group('A') one_or_more_lazy(noncap_group('A')) '(?:A)+?'
group_chars('A-Z') group(chars('A-Z')) '([A-Z])'
noncap_group_chars('A-Z') noncap_group(chars('A-Z')) '(?:[A-Z])'
group_nonchars('A-Z') group(nonchars('A-Z')) (['^A-Z])'
noncap_group_nonchars('A-Z') noncap_group(nonchars('A-Z')) (?:['^A-Z])'

Humre provides constants for the \d, \w, and \s character classes as well several other characters that need to be escaped:

Constant Regex Equivalent
DIGIT r'\d'
WORD r'\w'
WHITESPACE r'\s'
NONDIGIT r'\D'
NONWORD r'\W'
NONWHITESPACE r'\S'
BOUNDARY r'\b'
NEWLINE r'\n'
TAB r'\t'
QUOTE r"\'"
DOUBLE_QUOTE r'\"'
PERIOD r'\.'
CARET r'\^'
DOLLAR_SIGN r'\$'
ASTERISK r'\*'
PLUS_SIGN r'\+'
QUESTION_MARK r'\?'
OPEN_PARENTHESIS r'\('
OPEN_PAREN r'\('
CLOSE_PARENTHESIS r'\)'
CLOSE_PAREN r'\)'
OPEN_BRACE r'\{'
CLOSE_BRACE r'\}'
OPEN_BRACKET r'\['
CLOSE_BRACKET r'\]'
BACKSLASH r'\\'
PIPE r'|'
BACK_1 r'\1'
BACK_2 r'\2'
BACK_3 r'\3'
BACK_4 r'\4'
BACK_5 r'\5'
BACK_6 r'\6'
BACK_7 r'\7'
BACK_8 r'\8'
BACK_9 r'\9'

Humre also provides constants for commonly used patterns:

Humre Pattern Constants Regex Equivalent Note
ANYTHING '.*?' lazy "zero or more of anything" match
EVERYTHING '.*' greedy "zero or more of anything" match, aka dot star
SOMETHING '.+?' lazy "one or more of anything" match
ANYCHAR '.'
LETTER (too big to display) Matches isalpha()
NONLETTER (too big to display) Matches not isalpha()
UPPERCASE (too big to display) Matches isupper()
NONUPPERCASE (too big to display) Matches not isupper()
LOWERCASE (too big to display) Matches islower()
NONLOWERCASE (too big to display) Matches not islower()
ALPHANUMERIC (too big to display) Matches isalnum()
NONALPHANUMERIC (too big to display) Matches not isalnum()
HEXADECIMAL '[0-9A-Fa-f]'
NONHEXADECIMAL '[^0-9A-Fa-f]'
NUMBER r'(?:\+&#124;-)?(?:(?:\d{1,3}(?:,\d{3})+)&#124;\d+)(?:\.\d+)?' Comma-formatted numbers
EURO_NUMBER r'(?:\+&#124;-)?(?:(?:\d{1,3}(?:\.\d{3})+)&#124;\d+)(?:,\d+)?' Period-formatted numbers
HEXADECIMAL_NUMBER '(?:(?:0x&#124;0X)[0-9a-f]+)&#124;(?:(?:0x&#124;0X)[0-9A-F]+)&#124;(?:[0-9a-f]+)&#124;(?:[0-9A-F]+)' Can have leading 0x or 0X.

Humre's compile() function's flags keyword argument takes the same flag values as re.compile():

Humre compile() Flags Equivalent re.compile() Flags Meaning
humre.A re.A Same as ASCII
humre.ASCII re.ASCII Makes several escapes like \w, \b, \s and \d match only on ASCII characters with the respective property.
humre.DEBUG re.DEBUG Display debug information about compiled expression.
humre.I re.I Same as IGNORECASE
humre.IGNORECASE re.IGNORECASE Do case-insensitive matches.
humre.L re.L Same as LOCALE
humre.LOCALE re.LOCALE Do a locale-aware match.
humre.M re.M Same as MULTILINE
humre.MULTILINE re.MULTILINE Multi-line matching, affecting ^ and $.
humre.NOFLAG re.NOFLAG Indicates no flag being applied. Used as a default value in function definitions. New in 3.11.
humre.S re.S Same as DOTALL. Corresponds to the inline flag (?s).
humre.DOTALL re.DOTALL Make . match any character, including newlines.
humre.X re.X Same as VERBOSE (X stands for "extended")
humre.VERBOSE re.VERBOSE Enable verbose REs, which can be organized more cleanly and understandably.

More Repositories

1

pyperclip

Python module for cross-platform clipboard functions.
Python
1,450
star
2

PythonStdioGames

A collection of text-based games written in Python 3 that only use "standard i/o".
Python
863
star
3

my_first_tic_tac_toe

My first Tic Tac Toe program. I welcome any code reviews and pull requests!
Python
604
star
4

codebreaker

"Hacking Secret Ciphers with Python" programs
Python
330
star
5

PyGetWindow

A simple, cross-platform module for obtaining GUI information on applications' windows.
Python
284
star
6

ezgmail

A Pythonic interface to the Gmail API.
Python
232
star
7

pyscreeze

PyScreeze is a simple, cross-platform screenshot module for Python 2 and 3.
Python
158
star
8

sushigoroundbot

A bot that plays the Sushi Go Round flash game using PyAutoGUI.
Python
139
star
9

nicewin

A nicely-documented, pure-Python wrapper for the Windows API for Python 2 and 3.
Python
124
star
10

simple-turtle-tutorial-for-python

A simple tutorial for Python's turtle.py. This tutorial is meant to be easily translated into languages besides English.
Python
122
star
11

pytweening

A set of tweening / easing functions implemented in Python.
Jupyter Notebook
97
star
12

pyinputplus

The input() and raw_input() functions with added validation features for text-based programs.
Python
92
star
13

gamesbyexample

A collection of text-based games in Python and other languages.
Python
83
star
14

automatetheboringstuffwithpythondotcom

Source for the AutomateTheBoringStuff.com website.
HTML
72
star
15

ezsheets

A Pythonic interface to the Google Sheets API.
Python
63
star
16

inventwithpython3rded

Source text for "Invent Your Own Computer Games with Python, 3rd Edition"
Python
62
star
17

recursion_examples

Random source code files of recursive algorithms.
Python
60
star
18

PyMsgBox

Simple, cross-platform, pure Python module to display message boxes, and just message boxes.
Python
56
star
19

threadworms

A multithreaded programming demonstration in Python & Pygame using a "Nibbles" clone.
Python
55
star
20

idle-reimagined

IDLE Reimagined is the code name for a redesign for Python's default IDLE editor with focus as an educational tool.
HTML
54
star
21

zombiedice

A Python simulator for the dice game Zombie Dice that can run bot AI players.
Python
53
star
22

nes_zelda_map_data

A project to derive the map data for the original NES Legend of Zelda game so that it can be used in hobbyist projects.
Python
51
star
23

READAL

A readme for Al's GitHub repos. (Resume supplement)
49
star
24

making-games-with-python-and-pygame

The source code for the game programs for the book, "Making Games with Python & Pygame"
Python
49
star
25

art-of-turtle-programming

Various turtle drawing programs in Python
Python
46
star
26

textadventuredemo

Text Adventure Demo
Python
44
star
27

automateboringstuff

This package installs the modules used in "Automate the Boring Stuff with Python", 2nd Edition.
Python
35
star
28

pwinput

A cross-platform Python module that displays **** for password input. Works on Windows, unlike getpass. Formerly called stdiomask.
Python
35
star
29

imgur-hosted-reddit-posted-downloader

A Python script that checks Reddit for Imgur posts and downloads the corresponding images.
Python
30
star
30

pyganim

Sprite animation module for Pygame
Python
29
star
31

the-big-book-of-small-python-projects

The source code for the programs in "The Big Book of Small Python Projects"
Python
28
star
32

whatismyip

Fetch your public IP address from external sources with Python.
Python
28
star
33

mouseinfo

An application to display XY position and RGB color information for the pixel currently under the mouse. Works on Python 2 and 3.
Python
27
star
34

cookiecutter-basicpythonproject

A cookiecutter template for basic Python projects.
Python
26
star
35

moosegesture

Python 8-direction mouse gesture recognition library
Python
23
star
36

stdiomask

A cross-platform Python module for entering passwords and displaying a **** mask, which getpass cannot do.
Python
23
star
37

the-recursive-book-of-recursion

Source code for the programs in The Recursive Book of Recursion
Python
19
star
38

bext

A cross-platform Python 2/3 module for colorful, text-based terminal programs.
Python
18
star
39

pyaudacity

A Python module to control a running instance of Audacity through its macro system.
Python
18
star
40

pydidyoumean

A module to improve "file/command not found" error messages with "did you mean" suggestions.
Python
17
star
41

inventwithpythondotcom

The InventWithPython.com website source
HTML
16
star
42

basicsudoku

A simple, basic Sudoku class in Python. Suitable for programming tutorials or experimentation.
Python
15
star
43

mapitpy

Opens a browser window to maps.google.com based on your clipboard contents.
Python
15
star
44

bigbookpython

A module to install the dependencies for the projects in The Big Book of Small Python Projects
Python
14
star
45

PyKeyMouse

A simple, cross-platform Python 2/3 module to detect mouse and keyboard input.
Python
14
star
46

scrollart

A collection of "scroll art" computer animation programs in several languages, suitable for teaching/learning to code.
Python
13
star
47

pygbutton

A simple Button UI class for Pygame
Python
12
star
48

automateboringstuff3rdedition

This package installs the modules used in “Automate the Boring Stuff with Python”, 3rd Edition.
Python
11
star
49

automateboringstuff6thedition

This package installs the modules used in "Automate the Boring Stuff with Python", 6th Edition.
Python
10
star
50

pipfromrepl

Run pip to install packages from the Python interactive shell aka REPL.
Python
10
star
51

hobson

A GUI toolkit with simple features, take it or leave it. Cross-platform, text-based, pure Python 3.
Python
10
star
52

PyTextCanvas

A simple way to draw text and ascii art to a 2D grid "canvas" in Python.
Python
10
star
53

shelltut

A tutorial system for learning the shell/command line. For Windows and macOS/Linux.
10
star
54

automateboringstuff2ndedition

This package installs the modules used in “Automate the Boring Stuff with Python”, 2nd Edition.
Python
9
star
55

PyRect

PyRect is a simple module with a Rect class for Pygame-like rectangular areas.
Python
9
star
56

pysimplevalidate

A simple collection of validation functions that work on strings, suitable for use in other applications.
Python
9
star
57

asciiartjsondb

A collection of ASCII art, stored in a single JSON file.
Python
9
star
58

fluffygoggles

A Python app for taking a screenshot, reading the text with OCR, and copying the text to the clipboard. And it's free.
8
star
59

programmedpatterns

Practice exercises in pattern recognition, ideal for beginner and intermediate programmers.
Python
8
star
60

fractalartmaker

A module for creating fractal art in Python's turtle module.
Python
8
star
61

wordletools

A set of tools (written in Python) for solving Wordle puzzles.
Python
7
star
62

source_code_makeover

The "Invent with Python" Blog's Source Code Makeover project.
Python
7
star
63

gorillapy

Gorilla.py is a Python & Pygame remake of the classic QBasic game, gorilla.bas
7
star
64

ezdrive

A Pythonic interface to the Google Drive API.
Python
7
star
65

showcallstack

Shows a simplified view of the call stack.
Python
7
star
66

searchpolluter

A program that Googles random things to pollute your search history with nonsense.
7
star
67

capslockmorsecode

A silly module to control the keyboard LEDs of the CapsLock, NumLock, and ScrollLock keys to flash Morse Code on Windows, macOS, and Linux.
6
star
68

shortstr

A Python module to generate unambiguous strings for URL shortners or similar services.
Python
6
star
69

automateboringstuff1stedition

This package installs the modules used in “Automate the Boring Stuff with Python”, 1st Edition.
Python
6
star
70

automatepracticeprojects

Example implementations of the practice projects in Automate the Boring Stuff with Python
5
star
71

visualpatterns

Programming practice exercises in pattern recognition, written in Python. Ideal for beginner programmers.
Python
5
star
72

recursionbook

The programs used in Al Sweigart's book, "The Recursive Book of Recursion"
Python
5
star
73

showeval

A Python module to show the steps in evaluating an expression.
JavaScript
5
star
74

traceytext

JavaScript & HTML teaching tool to visualize single-stepping through source code.
Python
5
star
75

racistnotxenophobic

A Chrome extension which replaces the euphemistic word "xenophobic" with the more accurate "racist".
JavaScript
5
star
76

PyFuzzyBool

Additional boolean values: KindaTrue, KindaFalse, VeryTrue, and VeryFalse. (This is a joke project.)
Python
5
star
77

al-computercraft

Al's various Lua scripts for the Minecraft mod, ComputerCraft.
Lua
5
star
78

clear

A cross-platform Python module that provides a clear() function which clears the terminal. That's all.
Python
5
star
79

fpstimer

A clock timer that provides sleep()-like features for maintaining a certain "frames per second" (FPS) framerate in Python 2 and 3.
Python
5
star
80

pybresenham

A Python module of generators that generate x, y coordinates for various vector shapes such as lines, rectangles, etc. Named after Bresenham of line-algorithm fame.
Python
5
star
81

cliprec

A cross-platform clipboard monitoring and recording tool. Only works for text.
Python
5
star
82

ccwd

Windows command line executable to copy the current working directory to the clipboard.
C++
4
star
83

50AndroidHacksProjects

Android Studio projects that implement the 50 hacks in Carlos Sessa's "50 Android Hacks" book published by Manning.
Java
4
star
84

blankeditor

Code used in the Blank Editor screencast
Python
4
star
85

wizcoin

A Python module to represent the galleon, sickle, and knut coins of wizard currency.
Python
4
star
86

tortuga

Une mise en œuvre espagnole du module de turtle.py de Python.
Python
4
star
87

quicksort

An optimized implementation of the Quicksort algorithm in Python.
Python
4
star
88

drostedraw

A Python module for making recursive drawings (aka Droste effect) with the built-in turtle module.
Python
4
star
89

automateboringstuff3

4
star
90

whatismywifipassword

A Python 2 and 3 module that displays the password of your current wifi connection (and other features). Written for easy auditability.
4
star
91

mondrian_art_generator

A Python script that generates Mondrian-style art.
Python
3
star
92

simongesture

Python game using Pygame where the player must mimic patterns with mouse gestures. Uses the MooseGesture module.
Python
3
star
93

AlDoesProjectEuler

A record of my Project Euler solutions. Elegance Not Guaranteed.
Python
3
star
94

gridsandhexes

A Python module for producing PNG and PDF files of customized graph paper.
Python
3
star
95

bextjs

A boring text in-browser console, written in JavaScript.
HTML
3
star
96

sevseg

A simple Python module that produces ASCII art of seven-segment display numbers.
3
star
97

safefs

A Python module for safely working the file system in a safe, auditable way.
Python
3
star
98

pressplay

Cross-platform Python module to play mp3, wav, opus, and ogg audio sound files, built on Pygame.
3
star
99

boxstr

A Python module for text-based drawing using DOS "box-drawing" characters.
3
star
100

pygamereadthedocs

Pygame documentation for ReadTheDocs.org
Python
3
star