11 Commits

Author SHA1 Message Date
79bc467bf6 fix: styling
Some checks failed
Deploy Website / deploy-website (push) Successful in 28s
Release Standalone Builder / build (release) Successful in 29s
Release Standalone Builder / publish-aur (release) Failing after 4m6s
Release Standalone Builder / publish-homebrew (release) Successful in 7s
2026-05-05 20:50:59 +02:00
8a5fa202de fix: rsync permissions
All checks were successful
Deploy Website / deploy-website (push) Successful in 27s
2026-05-05 20:44:47 +02:00
c8df9a3da9 fix: navbar draft
Some checks failed
Release Standalone Builder / build (release) Successful in 1m50s
Deploy Website / deploy-website (push) Failing after 29s
Release Standalone Builder / publish-homebrew (release) Has been cancelled
Release Standalone Builder / publish-aur (release) Has been cancelled
2026-05-05 20:39:55 +02:00
b8e6c3afa8 feat: manifest/caching 2026-05-05 20:27:35 +02:00
3105c83290 feat: separate emoji file 2026-05-05 20:10:01 +02:00
206d9a650d feat/whatever: cleanup 2026-05-05 19:59:44 +02:00
105dce7d40 theming 2026-05-05 19:48:35 +02:00
21dc56aa6d docs: markdown
All checks were successful
Release Standalone Builder / build (release) Successful in 32s
Release Standalone Builder / publish-aur (release) Successful in 35s
Release Standalone Builder / publish-homebrew (release) Successful in 6s
2026-04-01 15:39:54 +02:00
7df5daaa6c branding: table styles 2026-04-01 15:24:37 +02:00
4f74dd5fe0 fix: !! 2026-04-01 15:09:24 +02:00
0751849492 docs: theming 2026-04-01 15:04:57 +02:00
40 changed files with 2082 additions and 1333 deletions

View File

@@ -0,0 +1,43 @@
name: Deploy Website
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy-website:
runs-on: local
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Ensure deploy tools are available
run: |
if ! command -v ssh >/dev/null 2>&1 || ! command -v rsync >/dev/null 2>&1 || ! command -v ssh-keyscan >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y openssh-client rsync
fi
- name: Build website
run: |
sh kewt.sh --from site --to out
- name: Setup SSH
run: |
mkdir -p ~/.ssh
printf '%s\n' "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
REMOTE_HOST_VAL="${{ secrets.REMOTE_HOST }}"
REMOTE_ADDR=$(printf '%s\n' "$REMOTE_HOST_VAL" | awk -F@ '{print $NF}')
ssh-keyscan -H "$REMOTE_ADDR" >> ~/.ssh/known_hosts
- name: Deploy website
run: |
rsync -rlz --delete --omit-dir-times \
--exclude '/logs/' \
-e "ssh -i ~/.ssh/id_rsa -o StrictHostKeyChecking=no" \
out/ "${{ secrets.REMOTE_HOST }}:/var/www/kewt.krzak.org/"

View File

@@ -1,20 +0,0 @@
name: Lint
on:
push:
branches: [main, master]
paths:
- '**.sh'
pull_request:
paths:
- '**.sh'
jobs:
shellcheck:
runs-on: local
steps:
- uses: actions/checkout@v4
- name: Install Shellcheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run Shellcheck
run: shellcheck -s sh kewt.sh markdown.sh tools/build-standalone.sh || true

View File

@@ -1,831 +1,15 @@
BEGIN { BEGIN {
map[":+1:"] = "👍" if (emoji_file == "") {
map[":100:"] = "💯" emoji_file = "emoji.tsv"
map[":1234:"] = "🔢" }
map[":8ball:"] = "🎱"
map[":a:"] = "🅰️" while ((getline line < emoji_file) > 0) {
map[":ab:"] = "🆎" split(line, parts, "\t")
map[":abc:"] = "🔤" if (length(parts[1]) > 0) {
map[":abcd:"] = "🔡" map[parts[1]] = parts[2]
map[":accept:"] = "🉑" }
map[":aerial_tramway:"] = "🚡" }
map[":airplane:"] = "✈️" close(emoji_file)
map[":alarm_clock:"] = "⏰"
map[":alien:"] = "👽"
map[":ambulance:"] = "🚑"
map[":anchor:"] = "⚓"
map[":angel:"] = "👼"
map[":anger:"] = "💢"
map[":angry:"] = "😠"
map[":anguished:"] = "😧"
map[":ant:"] = "🐜"
map[":apple:"] = "🍎"
map[":aquarius:"] = "♒"
map[":aries:"] = "♈"
map[":arrow_backward:"] = "◀️"
map[":arrow_double_down:"] = "⏬"
map[":arrow_double_up:"] = "⏫"
map[":arrow_down:"] = "⬇️"
map[":arrow_down_small:"] = "🔽"
map[":arrow_forward:"] = "▶️"
map[":arrow_heading_down:"] = "⤵️"
map[":arrow_heading_up:"] = "⤴️"
map[":arrow_left:"] = "⬅️"
map[":arrow_lower_left:"] = "↙️"
map[":arrow_lower_right:"] = "↘️"
map[":arrow_right:"] = "➡️"
map[":arrow_right_hook:"] = "↪️"
map[":arrow_up:"] = "⬆️"
map[":arrow_up_down:"] = "↕️"
map[":arrow_up_small:"] = "🔼"
map[":arrow_upper_left:"] = "↖️"
map[":arrow_upper_right:"] = "↗️"
map[":arrows_clockwise:"] = "🔃"
map[":arrows_counterclockwise:"] = "🔄"
map[":art:"] = "🎨"
map[":articulated_lorry:"] = "🚛"
map[":astonished:"] = "😲"
map[":atm:"] = "🏧"
map[":b:"] = "🅱️"
map[":baby:"] = "👶"
map[":baby_bottle:"] = "🍼"
map[":baby_chick:"] = "🐤"
map[":baby_symbol:"] = "🚼"
map[":baggage_claim:"] = "🛄"
map[":balloon:"] = "🎈"
map[":ballot_box_with_check:"] = "☑️"
map[":bamboo:"] = "🎍"
map[":banana:"] = "🍌"
map[":bangbang:"] = "‼️"
map[":bank:"] = "🏦"
map[":bar_chart:"] = "📊"
map[":barber:"] = "💈"
map[":baseball:"] = "⚾"
map[":basketball:"] = "🏀"
map[":bath:"] = "🛀"
map[":bathtub:"] = "🛁"
map[":battery:"] = "🔋"
map[":bear:"] = "🐻"
map[":beer:"] = "🍺"
map[":beers:"] = "🍻"
map[":beetle:"] = "🪲"
map[":beginner:"] = "🔰"
map[":bell:"] = "🔔"
map[":bento:"] = "🍱"
map[":bicyclist:"] = "🚴"
map[":bike:"] = "🚲"
map[":bikini:"] = "👙"
map[":bird:"] = "🐦"
map[":birthday:"] = "🎂"
map[":black_circle:"] = "⚫"
map[":black_joker:"] = "🃏"
map[":black_nib:"] = "✒️"
map[":black_square_button:"] = "🔲"
map[":blossom:"] = "🌼"
map[":blowfish:"] = "🐡"
map[":blue_book:"] = "📘"
map[":blue_car:"] = "🚙"
map[":blue_heart:"] = "💙"
map[":blush:"] = "😊"
map[":boar:"] = "🐗"
map[":boat:"] = "⛵"
map[":bomb:"] = "💣"
map[":book:"] = "📖"
map[":bookmark:"] = "🔖"
map[":bookmark_tabs:"] = "📑"
map[":books:"] = "📚"
map[":boom:"] = "💥"
map[":boot:"] = "👢"
map[":bouquet:"] = "💐"
map[":bow:"] = "🙇"
map[":bowling:"] = "🎳"
map[":boy:"] = "👦"
map[":bread:"] = "🍞"
map[":bride_with_veil:"] = "👰‍♀️"
map[":bridge_at_night:"] = "🌉"
map[":briefcase:"] = "💼"
map[":broken_heart:"] = "💔"
map[":bug:"] = "🐛"
map[":bulb:"] = "💡"
map[":bullettrain_front:"] = "🚅"
map[":bullettrain_side:"] = "🚄"
map[":bus:"] = "🚌"
map[":busstop:"] = "🚏"
map[":bust_in_silhouette:"] = "👤"
map[":busts_in_silhouette:"] = "👥"
map[":cactus:"] = "🌵"
map[":cake:"] = "🍰"
map[":calendar:"] = "📆"
map[":calling:"] = "📲"
map[":camel:"] = "🐫"
map[":camera:"] = "📷"
map[":cancer:"] = "♋"
map[":candy:"] = "🍬"
map[":capital_abcd:"] = "🔠"
map[":capricorn:"] = "♑"
map[":car:"] = "🚗"
map[":card_index:"] = "📇"
map[":carousel_horse:"] = "🎠"
map[":cat2:"] = "🐈"
map[":cat:"] = "🐱"
map[":cd:"] = "💿"
map[":chart:"] = "💹"
map[":chart_with_downwards_trend:"] = "📉"
map[":chart_with_upwards_trend:"] = "📈"
map[":checkered_flag:"] = "🏁"
map[":cherries:"] = "🍒"
map[":cherry_blossom:"] = "🌸"
map[":chestnut:"] = "🌰"
map[":chicken:"] = "🐔"
map[":children_crossing:"] = "🚸"
map[":chocolate_bar:"] = "🍫"
map[":christmas_tree:"] = "🎄"
map[":church:"] = "⛪"
map[":cinema:"] = "🎦"
map[":circus_tent:"] = "🎪"
map[":city_sunrise:"] = "🌇"
map[":city_sunset:"] = "🌆"
map[":cl:"] = "🆑"
map[":clap:"] = "👏"
map[":clapper:"] = "🎬"
map[":clipboard:"] = "📋"
map[":clock1030:"] = "🕥"
map[":clock10:"] = "🕙"
map[":clock1130:"] = "🕦"
map[":clock11:"] = "🕚"
map[":clock1230:"] = "🕧"
map[":clock12:"] = "🕛"
map[":clock130:"] = "🕜"
map[":clock1:"] = "🕐"
map[":clock230:"] = "🕝"
map[":clock2:"] = "🕑"
map[":clock330:"] = "🕞"
map[":clock3:"] = "🕒"
map[":clock430:"] = "🕟"
map[":clock4:"] = "🕓"
map[":clock530:"] = "🕠"
map[":clock5:"] = "🕔"
map[":clock630:"] = "🕡"
map[":clock6:"] = "🕕"
map[":clock730:"] = "🕢"
map[":clock7:"] = "🕖"
map[":clock830:"] = "🕣"
map[":clock8:"] = "🕗"
map[":clock930:"] = "🕤"
map[":clock9:"] = "🕘"
map[":closed_book:"] = "📕"
map[":closed_lock_with_key:"] = "🔐"
map[":closed_umbrella:"] = "🌂"
map[":cloud:"] = "☁️"
map[":clubs:"] = "♣️"
map[":cn:"] = "🇨🇳"
map[":cocktail:"] = "🍸"
map[":coffee:"] = "☕"
map[":cold_sweat:"] = "😰"
map[":computer:"] = "💻"
map[":confetti_ball:"] = "🎊"
map[":confounded:"] = "😖"
map[":confused:"] = "😕"
map[":congratulations:"] = "㊗️"
map[":construction:"] = "🚧"
map[":construction_worker:"] = "👷"
map[":convenience_store:"] = "🏪"
map[":cookie:"] = "🍪"
map[":cool:"] = "🆒"
map[":cop:"] = "👮"
map[":copyright:"] = "©️"
map[":corn:"] = "🌽"
map[":couple:"] = "👫"
map[":couple_with_heart:"] = "💑"
map[":couplekiss:"] = "💏"
map[":cow2:"] = "🐄"
map[":cow:"] = "🐮"
map[":credit_card:"] = "💳"
map[":crocodile:"] = "🐊"
map[":crossed_flags:"] = "🎌"
map[":crown:"] = "👑"
map[":cry:"] = "😢"
map[":crying_cat_face:"] = "😿"
map[":crystal_ball:"] = "🔮"
map[":cupid:"] = "💘"
map[":curly_loop:"] = "➰"
map[":currency_exchange:"] = "💱"
map[":curry:"] = "🍛"
map[":custard:"] = "🍮"
map[":customs:"] = "🛃"
map[":cyclone:"] = "🌀"
map[":dancer:"] = "💃"
map[":dancers:"] = "👯"
map[":dango:"] = "🍡"
map[":dart:"] = "🎯"
map[":dash:"] = "💨"
map[":date:"] = "📅"
map[":de:"] = "🇩🇪"
map[":deciduous_tree:"] = "🌳"
map[":department_store:"] = "🏬"
map[":diamond_shape_with_a_dot_inside:"] = "💠"
map[":diamonds:"] = "♦️"
map[":disappointed:"] = "😞"
map[":disappointed_relieved:"] = "😥"
map[":dizzy:"] = "💫"
map[":dizzy_face:"] = "😵"
map[":do_not_litter:"] = "🚯"
map[":dog2:"] = "🐕"
map[":dog:"] = "🐶"
map[":dollar:"] = "💵"
map[":dolls:"] = "🎎"
map[":dolphin:"] = "🐬"
map[":door:"] = "🚪"
map[":doughnut:"] = "🍩"
map[":dragon:"] = "🐉"
map[":dragon_face:"] = "🐲"
map[":dress:"] = "👗"
map[":dromedary_camel:"] = "🐪"
map[":droplet:"] = "💧"
map[":dvd:"] = "📀"
map[":ear:"] = "👂"
map[":ear_of_rice:"] = "🌾"
map[":earth_africa:"] = "🌍"
map[":earth_americas:"] = "🌎"
map[":earth_asia:"] = "🌏"
map[":egg:"] = "🥚"
map[":eggplant:"] = "🍆"
map[":eight:"] = "8⃣"
map[":eight_pointed_black_star:"] = "✴️"
map[":eight_spoked_asterisk:"] = "✳️"
map[":electric_plug:"] = "🔌"
map[":elephant:"] = "🐘"
map[":email:"] = "📧"
map[":end:"] = "🔚"
map[":envelope:"] = "✉️"
map[":es:"] = "🇪🇸"
map[":euro:"] = "💶"
map[":european_castle:"] = "🏰"
map[":european_post_office:"] = "🏤"
map[":evergreen_tree:"] = "🌲"
map[":exclamation:"] = "❗"
map[":expressionless:"] = "😑"
map[":eyeglasses:"] = "👓"
map[":eyes:"] = "👀"
map[":factory:"] = "🏭"
map[":fallen_leaf:"] = "🍂"
map[":family:"] = "👪"
map[":fast_forward:"] = "⏩"
map[":fax:"] = "📠"
map[":fearful:"] = "😨"
map[":feet:"] = "🐾"
map[":ferris_wheel:"] = "🎡"
map[":file_folder:"] = "📁"
map[":fire:"] = "🔥"
map[":fire_engine:"] = "🚒"
map[":fireworks:"] = "🎆"
map[":first_quarter_moon:"] = "🌓"
map[":first_quarter_moon_with_face:"] = "🌛"
map[":fish:"] = "🐟"
map[":fish_cake:"] = "🍥"
map[":fishing_pole_and_fish:"] = "🎣"
map[":fist:"] = "✊"
map[":five:"] = "5⃣"
map[":flags:"] = "🎏"
map[":flashlight:"] = "🔦"
map[":floppy_disk:"] = "💾"
map[":flower_playing_cards:"] = "🎴"
map[":flushed:"] = "😳"
map[":foggy:"] = "🌁"
map[":football:"] = "🏈"
map[":fork_and_knife:"] = "🍴"
map[":fountain:"] = "⛲"
map[":four:"] = "4⃣"
map[":four_leaf_clover:"] = "🍀"
map[":fr:"] = "🇫🇷"
map[":free:"] = "🆓"
map[":fried_shrimp:"] = "🍤"
map[":fries:"] = "🍟"
map[":frog:"] = "🐸"
map[":frowning:"] = "😦"
map[":fuelpump:"] = "⛽"
map[":full_moon:"] = "🌕"
map[":full_moon_with_face:"] = "🌝"
map[":game_die:"] = "🎲"
map[":gem:"] = "💎"
map[":gemini:"] = "♊"
map[":ghost:"] = "👻"
map[":gift:"] = "🎁"
map[":gift_heart:"] = "💝"
map[":girl:"] = "👧"
map[":globe_with_meridians:"] = "🌐"
map[":goat:"] = "🐐"
map[":golf:"] = "⛳"
map[":grapes:"] = "🍇"
map[":green_apple:"] = "🍏"
map[":green_book:"] = "📗"
map[":green_heart:"] = "💚"
map[":grey_exclamation:"] = "❕"
map[":grey_question:"] = "❔"
map[":grimacing:"] = "😬"
map[":grin:"] = "😁"
map[":grinning:"] = "😀"
map[":guardsman:"] = "💂‍♂️"
map[":guitar:"] = "🎸"
map[":gun:"] = "🔫"
map[":haircut:"] = "💇"
map[":hamburger:"] = "🍔"
map[":hammer:"] = "🔨"
map[":hamster:"] = "🐹"
map[":handbag:"] = "👜"
map[":hankey:"] = "💩"
map[":hash:"] = "#️⃣"
map[":hatched_chick:"] = "🐥"
map[":hatching_chick:"] = "🐣"
map[":headphones:"] = "🎧"
map[":hear_no_evil:"] = "🙉"
map[":heart:"] = "❤️"
map[":heart_decoration:"] = "💟"
map[":heart_eyes:"] = "😍"
map[":heart_eyes_cat:"] = "😻"
map[":heartbeat:"] = "💓"
map[":hearts:"] = "♥️"
map[":heavy_check_mark:"] = "✔️"
map[":heavy_division_sign:"] = "➗"
map[":heavy_dollar_sign:"] = "💲"
map[":heavy_minus_sign:"] = ""
map[":heavy_multiplication_x:"] = "✖️"
map[":heavy_plus_sign:"] = ""
map[":helicopter:"] = "🚁"
map[":herb:"] = "🌿"
map[":hibiscus:"] = "🌺"
map[":high_brightness:"] = "🔆"
map[":high_heel:"] = "👠"
map[":honey_pot:"] = "🍯"
map[":horse:"] = "🐴"
map[":horse_racing:"] = "🏇"
map[":hospital:"] = "🏥"
map[":hotel:"] = "🏨"
map[":hotsprings:"] = "♨️"
map[":hourglass:"] = "⌛"
map[":hourglass_flowing_sand:"] = "⏳"
map[":house:"] = "🏠"
map[":house_with_garden:"] = "🏡"
map[":hushed:"] = "😯"
map[":ice_cream:"] = "🍨"
map[":icecream:"] = "🍦"
map[":id:"] = "🆔"
map[":ideograph_advantage:"] = "🉐"
map[":imp:"] = "👿"
map[":inbox_tray:"] = "📥"
map[":incoming_envelope:"] = "📨"
map[":information_desk_person:"] = "💁"
map[":information_source:"] = ""
map[":innocent:"] = "😇"
map[":interrobang:"] = "⁉️"
map[":iphone:"] = "📱"
map[":it:"] = "🇮🇹"
map[":izakaya_lantern:"] = "🏮"
map[":jack_o_lantern:"] = "🎃"
map[":japan:"] = "🗾"
map[":japanese_castle:"] = "🏯"
map[":japanese_goblin:"] = "👺"
map[":japanese_ogre:"] = "👹"
map[":jeans:"] = "👖"
map[":joy:"] = "😂"
map[":joy_cat:"] = "😹"
map[":jp:"] = "🇯🇵"
map[":key:"] = "🔑"
map[":keycap_ten:"] = "🔟"
map[":kimono:"] = "👘"
map[":kiss:"] = "💋"
map[":kissing:"] = "😗"
map[":kissing_cat:"] = "😽"
map[":kissing_closed_eyes:"] = "😚"
map[":kissing_heart:"] = "😘"
map[":kissing_smiling_eyes:"] = "😙"
map[":koala:"] = "🐨"
map[":koko:"] = "🈁"
map[":kr:"] = "🇰🇷"
map[":large_blue_diamond:"] = "🔷"
map[":large_orange_diamond:"] = "🔶"
map[":last_quarter_moon:"] = "🌗"
map[":last_quarter_moon_with_face:"] = "🌜"
map[":laughing:"] = "😆"
map[":leaves:"] = "🍃"
map[":ledger:"] = "📒"
map[":left_luggage:"] = "🛅"
map[":left_right_arrow:"] = "↔️"
map[":leftwards_arrow_with_hook:"] = "↩️"
map[":lemon:"] = "🍋"
map[":leo:"] = "♌"
map[":leopard:"] = "🐆"
map[":libra:"] = "♎"
map[":light_rail:"] = "🚈"
map[":link:"] = "🔗"
map[":lips:"] = "👄"
map[":lipstick:"] = "💄"
map[":lock:"] = "🔒"
map[":lock_with_ink_pen:"] = "🔏"
map[":lollipop:"] = "🍭"
map[":loop:"] = "➿"
map[":loudspeaker:"] = "📢"
map[":love_hotel:"] = "🏩"
map[":love_letter:"] = "💌"
map[":low_brightness:"] = "🔅"
map[":m:"] = "Ⓜ️"
map[":mag:"] = "🔍"
map[":mag_right:"] = "🔎"
map[":mahjong:"] = "🀄"
map[":mailbox:"] = "📫"
map[":mailbox_closed:"] = "📪"
map[":mailbox_with_mail:"] = "📬"
map[":mailbox_with_no_mail:"] = "📭"
map[":man:"] = "👨"
map[":man_with_gua_pi_mao:"] = "👲"
map[":man_with_turban:"] = "👳‍♂️"
map[":mans_shoe:"] = "👞"
map[":maple_leaf:"] = "🍁"
map[":mask:"] = "😷"
map[":massage:"] = "💆"
map[":meat_on_bone:"] = "🍖"
map[":mega:"] = "📣"
map[":melon:"] = "🍈"
map[":memo:"] = "📝"
map[":mens:"] = "🚹"
map[":metal:"] = "🤘"
map[":metro:"] = "🚇"
map[":microphone:"] = "🎤"
map[":microscope:"] = "🔬"
map[":milky_way:"] = "🌌"
map[":minibus:"] = "🚐"
map[":minidisc:"] = "💽"
map[":mobile_phone_off:"] = "📴"
map[":money_with_wings:"] = "💸"
map[":moneybag:"] = "💰"
map[":monkey:"] = "🐒"
map[":monkey_face:"] = "🐵"
map[":monorail:"] = "🚝"
map[":mortar_board:"] = "🎓"
map[":mount_fuji:"] = "🗻"
map[":mountain_bicyclist:"] = "🚵"
map[":mountain_cableway:"] = "🚠"
map[":mountain_railway:"] = "🚞"
map[":mouse2:"] = "🐁"
map[":mouse:"] = "🐭"
map[":movie_camera:"] = "🎥"
map[":moyai:"] = "🗿"
map[":muscle:"] = "💪"
map[":mushroom:"] = "🍄"
map[":musical_keyboard:"] = "🎹"
map[":musical_note:"] = "🎵"
map[":musical_score:"] = "🎼"
map[":mute:"] = "🔇"
map[":nail_care:"] = "💅"
map[":name_badge:"] = "📛"
map[":necktie:"] = "👔"
map[":negative_squared_cross_mark:"] = "❎"
map[":neutral_face:"] = "😐"
map[":new:"] = "🆕"
map[":new_moon:"] = "🌑"
map[":new_moon_with_face:"] = "🌚"
map[":newspaper:"] = "📰"
map[":ng:"] = "🆖"
map[":nine:"] = "9⃣"
map[":no_bell:"] = "🔕"
map[":no_bicycles:"] = "🚳"
map[":no_entry:"] = "⛔"
map[":no_entry_sign:"] = "🚫"
map[":no_good:"] = "🙅"
map[":no_mobile_phones:"] = "📵"
map[":no_mouth:"] = "😶"
map[":no_pedestrians:"] = "🚷"
map[":no_smoking:"] = "🚭"
map[":nose:"] = "👃"
map[":notebook:"] = "📓"
map[":notebook_with_decorative_cover:"] = "📔"
map[":notes:"] = "🎶"
map[":nut_and_bolt:"] = "🔩"
map[":o2:"] = "🅾️"
map[":o:"] = "⭕"
map[":ocean:"] = "🌊"
map[":octopus:"] = "🐙"
map[":oden:"] = "🍢"
map[":office:"] = "🏢"
map[":ok:"] = "🆗"
map[":ok_hand:"] = "👌"
map[":ok_woman:"] = "🙆‍♀️"
map[":older_man:"] = "👴"
map[":older_woman:"] = "👵"
map[":on:"] = "🔛"
map[":oncoming_automobile:"] = "🚘"
map[":oncoming_bus:"] = "🚍"
map[":oncoming_police_car:"] = "🚔"
map[":oncoming_taxi:"] = "🚖"
map[":one:"] = "1⃣"
map[":open_file_folder:"] = "📂"
map[":open_hands:"] = "👐"
map[":open_mouth:"] = "😮"
map[":ophiuchus:"] = "⛎"
map[":orange_book:"] = "📙"
map[":outbox_tray:"] = "📤"
map[":ox:"] = "🐂"
map[":page_facing_up:"] = "📄"
map[":page_with_curl:"] = "📃"
map[":pager:"] = "📟"
map[":palm_tree:"] = "🌴"
map[":panda_face:"] = "🐼"
map[":paperclip:"] = "📎"
map[":parking:"] = "🅿️"
map[":part_alternation_mark:"] = "〽️"
map[":partly_sunny:"] = "⛅"
map[":passport_control:"] = "🛂"
map[":paw_prints:"] = "🐾"
map[":peach:"] = "🍑"
map[":pear:"] = "🍐"
map[":pencil2:"] = "✏️"
map[":pencil:"] = "📝"
map[":penguin:"] = "🐧"
map[":pensive:"] = "😔"
map[":performing_arts:"] = "🎭"
map[":persevere:"] = "😣"
map[":pig2:"] = "🐖"
map[":pig:"] = "🐷"
map[":pig_nose:"] = "🐽"
map[":pill:"] = "💊"
map[":pineapple:"] = "🍍"
map[":pisces:"] = "♓"
map[":pizza:"] = "🍕"
map[":point_down:"] = "👇"
map[":point_left:"] = "👈"
map[":point_right:"] = "👉"
map[":point_up:"] = "☝️"
map[":point_up_2:"] = "👆"
map[":police_car:"] = "🚓"
map[":poodle:"] = "🐩"
map[":poop:"] = "💩"
map[":post_office:"] = "🏣"
map[":postal_horn:"] = "📯"
map[":postbox:"] = "📮"
map[":potable_water:"] = "🚰"
map[":pouch:"] = "👝"
map[":poultry_leg:"] = "🍗"
map[":pound:"] = "💷"
map[":pouting_cat:"] = "😾"
map[":pray:"] = "🙏"
map[":princess:"] = "👸"
map[":punch:"] = "👊"
map[":purple_heart:"] = "💜"
map[":purse:"] = "👛"
map[":pushpin:"] = "📌"
map[":put_litter_in_its_place:"] = "🚮"
map[":question:"] = "❓"
map[":rabbit2:"] = "🐇"
map[":rabbit:"] = "🐰"
map[":racehorse:"] = "🐎"
map[":radio:"] = "📻"
map[":radio_button:"] = "🔘"
map[":rage:"] = "😡"
map[":railway_car:"] = "🚃"
map[":rainbow:"] = "🌈"
map[":raised_hand:"] = "✋"
map[":raised_hands:"] = "🙌"
map[":raising_hand:"] = "🙋"
map[":ram:"] = "🐏"
map[":ramen:"] = "🍜"
map[":rat:"] = "🐀"
map[":recycle:"] = "♻️"
map[":red_car:"] = "🚗"
map[":red_circle:"] = "🔴"
map[":registered:"] = "®️"
map[":relaxed:"] = "☺️"
map[":relieved:"] = "😌"
map[":repeat:"] = "🔁"
map[":repeat_one:"] = "🔂"
map[":restroom:"] = "🚻"
map[":revolving_hearts:"] = "💞"
map[":rewind:"] = "⏪"
map[":ribbon:"] = "🎀"
map[":rice:"] = "🍚"
map[":rice_ball:"] = "🍙"
map[":rice_cracker:"] = "🍘"
map[":rice_scene:"] = "🎑"
map[":ring:"] = "💍"
map[":rocket:"] = "🚀"
map[":roller_coaster:"] = "🎢"
map[":rooster:"] = "🐓"
map[":rose:"] = "🌹"
map[":rotating_light:"] = "🚨"
map[":round_pushpin:"] = "📍"
map[":rowboat:"] = "🚣"
map[":ru:"] = "🇷🇺"
map[":rugby_football:"] = "🏉"
map[":runner:"] = "🏃"
map[":running_shirt_with_sash:"] = "🎽"
map[":sa:"] = "🈂️"
map[":sagittarius:"] = "♐"
map[":sailboat:"] = "⛵"
map[":sake:"] = "🍶"
map[":sandal:"] = "👡"
map[":santa:"] = "🎅"
map[":satellite:"] = "📡"
map[":satisfied:"] = "😆"
map[":saxophone:"] = "🎷"
map[":school:"] = "🏫"
map[":school_satchel:"] = "🎒"
map[":scissors:"] = "✂️"
map[":scorpius:"] = "♏"
map[":scream:"] = "😱"
map[":scream_cat:"] = "🙀"
map[":scroll:"] = "📜"
map[":seat:"] = "💺"
map[":secret:"] = "㊙️"
map[":see_no_evil:"] = "🙈"
map[":seedling:"] = "🌱"
map[":seven:"] = "7⃣"
map[":shaved_ice:"] = "🍧"
map[":sheep:"] = "🐑"
map[":shell:"] = "🐚"
map[":ship:"] = "🚢"
map[":shirt:"] = "👕"
map[":shit:"] = "💩"
map[":shower:"] = "🚿"
map[":signal_strength:"] = "📶"
map[":six:"] = "6⃣"
map[":six_pointed_star:"] = "🔯"
map[":ski:"] = "🎿"
map[":skull:"] = "💀"
map[":sleeping:"] = "😴"
map[":sleepy:"] = "😪"
map[":slot_machine:"] = "🎰"
map[":small_blue_diamond:"] = "🔹"
map[":small_orange_diamond:"] = "🔸"
map[":small_red_triangle:"] = "🔺"
map[":small_red_triangle_down:"] = "🔻"
map[":smile:"] = "😄"
map[":smile_cat:"] = "😸"
map[":smiley:"] = "😃"
map[":smiley_cat:"] = "😺"
map[":smiling_imp:"] = "😈"
map[":smirk:"] = "😏"
map[":smirk_cat:"] = "😼"
map[":smoking:"] = "🚬"
map[":snail:"] = "🐌"
map[":snake:"] = "🐍"
map[":snowboarder:"] = "🏂"
map[":snowflake:"] = "❄️"
map[":snowman:"] = "⛄"
map[":sob:"] = "😭"
map[":soccer:"] = "⚽"
map[":soon:"] = "🔜"
map[":sos:"] = "🆘"
map[":sound:"] = "🔉"
map[":space_invader:"] = "👾"
map[":spades:"] = "♠️"
map[":spaghetti:"] = "🍝"
map[":sparkler:"] = "🎇"
map[":sparkles:"] = "✨"
map[":sparkling_heart:"] = "💖"
map[":speak_no_evil:"] = "🙊"
map[":speaker:"] = "🔈"
map[":speech_balloon:"] = "💬"
map[":speedboat:"] = "🚤"
map[":star2:"] = "🌟"
map[":star:"] = "⭐"
map[":stars:"] = "🌠"
map[":station:"] = "🚉"
map[":statue_of_liberty:"] = "🗽"
map[":steam_locomotive:"] = "🚂"
map[":stew:"] = "🍲"
map[":straight_ruler:"] = "📏"
map[":strawberry:"] = "🍓"
map[":stuck_out_tongue:"] = "😛"
map[":stuck_out_tongue_closed_eyes:"] = "😝"
map[":stuck_out_tongue_winking_eye:"] = "😜"
map[":sun_with_face:"] = "🌞"
map[":sunflower:"] = "🌻"
map[":sunglasses:"] = "😎"
map[":sunny:"] = "☀️"
map[":sunrise:"] = "🌅"
map[":sunrise_over_mountains:"] = "🌄"
map[":surfer:"] = "🏄"
map[":sushi:"] = "🍣"
map[":suspension_railway:"] = "🚟"
map[":sweat:"] = "😓"
map[":sweat_drops:"] = "💦"
map[":sweat_smile:"] = "😅"
map[":sweet_potato:"] = "🍠"
map[":swimmer:"] = "🏊"
map[":symbols:"] = "🔣"
map[":syringe:"] = "💉"
map[":tada:"] = "🎉"
map[":tanabata_tree:"] = "🎋"
map[":tangerine:"] = "🍊"
map[":taurus:"] = "♉"
map[":taxi:"] = "🚕"
map[":tea:"] = "🍵"
map[":telephone:"] = "☎️"
map[":telephone_receiver:"] = "📞"
map[":telescope:"] = "🔭"
map[":tennis:"] = "🎾"
map[":tent:"] = "⛺"
map[":thought_balloon:"] = "💭"
map[":three:"] = "3⃣"
map[":thumbsdown:"] = "👎"
map[":thumbsup:"] = "👍"
map[":ticket:"] = "🎫"
map[":tiger2:"] = "🐅"
map[":tiger:"] = "🐯"
map[":tired_face:"] = "😫"
map[":tm:"] = "™️"
map[":toilet:"] = "🚽"
map[":tokyo_tower:"] = "🗼"
map[":tomato:"] = "🍅"
map[":tongue:"] = "👅"
map[":top:"] = "🔝"
map[":tophat:"] = "🎩"
map[":tractor:"] = "🚜"
map[":traffic_light:"] = "🚥"
map[":train2:"] = "🚆"
map[":train:"] = "🚋"
map[":tram:"] = "🚊"
map[":triangular_flag_on_post:"] = "🚩"
map[":triangular_ruler:"] = "📐"
map[":trident:"] = "🔱"
map[":triumph:"] = "😤"
map[":trolleybus:"] = "🚎"
map[":trophy:"] = "🏆"
map[":tropical_drink:"] = "🍹"
map[":tropical_fish:"] = "🐠"
map[":truck:"] = "🚚"
map[":trumpet:"] = "🎺"
map[":tulip:"] = "🌷"
map[":turtle:"] = "🐢"
map[":tv:"] = "📺"
map[":twisted_rightwards_arrows:"] = "🔀"
map[":two:"] = "2⃣"
map[":two_men_holding_hands:"] = "👬"
map[":two_women_holding_hands:"] = "👭"
map[":u5272:"] = "🈹"
map[":u5408:"] = "🈴"
map[":u55b6:"] = "🈺"
map[":u6307:"] = "🈯"
map[":u6708:"] = "🈷️"
map[":u6709:"] = "🈶"
map[":u6e80:"] = "🈵"
map[":u7121:"] = "🈚"
map[":u7533:"] = "🈸"
map[":u7981:"] = "🈲"
map[":u7a7a:"] = "🈳"
map[":umbrella:"] = "☔"
map[":unamused:"] = "😒"
map[":underage:"] = "🔞"
map[":unlock:"] = "🔓"
map[":up:"] = "🆙"
map[":us:"] = "🇺🇸"
map[":v:"] = "✌️"
map[":vertical_traffic_light:"] = "🚦"
map[":vhs:"] = "📼"
map[":vibration_mode:"] = "📳"
map[":video_camera:"] = "📹"
map[":video_game:"] = "🎮"
map[":violin:"] = "🎻"
map[":virgo:"] = "♍"
map[":volcano:"] = "🌋"
map[":vs:"] = "🆚"
map[":walking:"] = "🚶"
map[":waning_crescent_moon:"] = "🌘"
map[":waning_gibbous_moon:"] = "🌖"
map[":warning:"] = "⚠️"
map[":watch:"] = "⌚"
map[":water_buffalo:"] = "🐃"
map[":watermelon:"] = "🍉"
map[":wave:"] = "👋"
map[":wavy_dash:"] = "〰️"
map[":waxing_crescent_moon:"] = "🌒"
map[":waxing_gibbous_moon:"] = "🌔"
map[":wc:"] = "🚾"
map[":weary:"] = "😩"
map[":wedding:"] = "💒"
map[":whale2:"] = "🐋"
map[":whale:"] = "🐳"
map[":wheelchair:"] = "♿"
map[":white_check_mark:"] = "✅"
map[":white_circle:"] = "⚪"
map[":white_flower:"] = "💮"
map[":white_square_button:"] = "🔳"
map[":wind_chime:"] = "🎐"
map[":wine_glass:"] = "🍷"
map[":wink:"] = "😉"
map[":wolf:"] = "🐺"
map[":woman:"] = "👩"
map[":womans_clothes:"] = "👚"
map[":womans_hat:"] = "👒"
map[":womens:"] = "🚺"
map[":worried:"] = "😟"
map[":wrench:"] = "🔧"
map[":x:"] = "❌"
map[":yellow_heart:"] = "💛"
map[":yen:"] = "💴"
map[":yum:"] = "😋"
map[":zap:"] = "⚡"
map[":zero:"] = "0⃣"
map[":zzz:"] = "💤"
} }
{ {
if ($0 ~ /<pre>/) in_pre = 1 if ($0 ~ /<pre>/) in_pre = 1
@@ -833,6 +17,7 @@ BEGIN {
code_count = 0 code_count = 0
line = $0 line = $0
out = "" out = ""
while (match(line, /<code>[^<]*<\/code>/)) { while (match(line, /<code>[^<]*<\/code>/)) {
code_count++ code_count++
code_store[code_count] = substr(line, RSTART, RLENGTH) code_store[code_count] = substr(line, RSTART, RLENGTH)
@@ -841,14 +26,23 @@ BEGIN {
} }
out = out line out = out line
for (sc in map) { line = out
if (index(out, sc)) { out = ""
gsub(sc, map[sc], out) while (match(line, /:[A-Za-z0-9_+\-]+:/)) {
token = substr(line, RSTART, RLENGTH)
out = out substr(line, 1, RSTART - 1)
if (token in map) {
out = out map[token]
} else {
out = out token
} }
line = substr(line, RSTART + RLENGTH)
} }
out = out line
for (i = 1; i <= code_count; i++) { for (i = 1; i <= code_count; i++) {
gsub("\034EC" i "\034", code_store[i], out) gsub("\034EC" i "\034", code_store[i], out)
delete code_store[i]
} }
$0 = out $0 = out
} }

827
awk/emoji.tsv Normal file
View File

@@ -0,0 +1,827 @@
:+1: 👍
:100: 💯
:1234: 🔢
:8ball: 🎱
:a: 🅰️
:ab: 🆎
:abc: 🔤
:abcd: 🔡
:accept: 🉑
:aerial_tramway: 🚡
:airplane: ✈️
:alarm_clock: ⏰
:alien: 👽
:ambulance: 🚑
:anchor: ⚓
:angel: 👼
:anger: 💢
:angry: 😠
:anguished: 😧
:ant: 🐜
:apple: 🍎
:aquarius: ♒
:aries: ♈
:arrow_backward: ◀️
:arrow_double_down: ⏬
:arrow_double_up: ⏫
:arrow_down: ⬇️
:arrow_down_small: 🔽
:arrow_forward: ▶️
:arrow_heading_down: ⤵️
:arrow_heading_up: ⤴️
:arrow_left: ⬅️
:arrow_lower_left: ↙️
:arrow_lower_right: ↘️
:arrow_right: ➡️
:arrow_right_hook: ↪️
:arrow_up: ⬆️
:arrow_up_down: ↕️
:arrow_up_small: 🔼
:arrow_upper_left: ↖️
:arrow_upper_right: ↗️
:arrows_clockwise: 🔃
:arrows_counterclockwise: 🔄
:art: 🎨
:articulated_lorry: 🚛
:astonished: 😲
:atm: 🏧
:b: 🅱️
:baby: 👶
:baby_bottle: 🍼
:baby_chick: 🐤
:baby_symbol: 🚼
:baggage_claim: 🛄
:balloon: 🎈
:ballot_box_with_check: ☑️
:bamboo: 🎍
:banana: 🍌
:bangbang: ‼️
:bank: 🏦
:bar_chart: 📊
:barber: 💈
:baseball: ⚾
:basketball: 🏀
:bath: 🛀
:bathtub: 🛁
:battery: 🔋
:bear: 🐻
:beer: 🍺
:beers: 🍻
:beetle: 🪲
:beginner: 🔰
:bell: 🔔
:bento: 🍱
:bicyclist: 🚴
:bike: 🚲
:bikini: 👙
:bird: 🐦
:birthday: 🎂
:black_circle: ⚫
:black_joker: 🃏
:black_nib: ✒️
:black_square_button: 🔲
:blossom: 🌼
:blowfish: 🐡
:blue_book: 📘
:blue_car: 🚙
:blue_heart: 💙
:blush: 😊
:boar: 🐗
:boat: ⛵
:bomb: 💣
:book: 📖
:bookmark: 🔖
:bookmark_tabs: 📑
:books: 📚
:boom: 💥
:boot: 👢
:bouquet: 💐
:bow: 🙇
:bowling: 🎳
:boy: 👦
:bread: 🍞
:bride_with_veil: 👰‍♀️
:bridge_at_night: 🌉
:briefcase: 💼
:broken_heart: 💔
:bug: 🐛
:bulb: 💡
:bullettrain_front: 🚅
:bullettrain_side: 🚄
:bus: 🚌
:busstop: 🚏
:bust_in_silhouette: 👤
:busts_in_silhouette: 👥
:cactus: 🌵
:cake: 🍰
:calendar: 📆
:calling: 📲
:camel: 🐫
:camera: 📷
:cancer: ♋
:candy: 🍬
:capital_abcd: 🔠
:capricorn: ♑
:car: 🚗
:card_index: 📇
:carousel_horse: 🎠
:cat2: 🐈
:cat: 🐱
:cd: 💿
:chart: 💹
:chart_with_downwards_trend: 📉
:chart_with_upwards_trend: 📈
:checkered_flag: 🏁
:cherries: 🍒
:cherry_blossom: 🌸
:chestnut: 🌰
:chicken: 🐔
:children_crossing: 🚸
:chocolate_bar: 🍫
:christmas_tree: 🎄
:church: ⛪
:cinema: 🎦
:circus_tent: 🎪
:city_sunrise: 🌇
:city_sunset: 🌆
:cl: 🆑
:clap: 👏
:clapper: 🎬
:clipboard: 📋
:clock1030: 🕥
:clock10: 🕙
:clock1130: 🕦
:clock11: 🕚
:clock1230: 🕧
:clock12: 🕛
:clock130: 🕜
:clock1: 🕐
:clock230: 🕝
:clock2: 🕑
:clock330: 🕞
:clock3: 🕒
:clock430: 🕟
:clock4: 🕓
:clock530: 🕠
:clock5: 🕔
:clock630: 🕡
:clock6: 🕕
:clock730: 🕢
:clock7: 🕖
:clock830: 🕣
:clock8: 🕗
:clock930: 🕤
:clock9: 🕘
:closed_book: 📕
:closed_lock_with_key: 🔐
:closed_umbrella: 🌂
:cloud: ☁️
:clubs: ♣️
:cn: 🇨🇳
:cocktail: 🍸
:coffee: ☕
:cold_sweat: 😰
:computer: 💻
:confetti_ball: 🎊
:confounded: 😖
:confused: 😕
:congratulations: ㊗️
:construction: 🚧
:construction_worker: 👷
:convenience_store: 🏪
:cookie: 🍪
:cool: 🆒
:cop: 👮
:copyright: ©️
:corn: 🌽
:couple: 👫
:couple_with_heart: 💑
:couplekiss: 💏
:cow2: 🐄
:cow: 🐮
:credit_card: 💳
:crocodile: 🐊
:crossed_flags: 🎌
:crown: 👑
:cry: 😢
:crying_cat_face: 😿
:crystal_ball: 🔮
:cupid: 💘
:curly_loop: ➰
:currency_exchange: 💱
:curry: 🍛
:custard: 🍮
:customs: 🛃
:cyclone: 🌀
:dancer: 💃
:dancers: 👯
:dango: 🍡
:dart: 🎯
:dash: 💨
:date: 📅
:de: 🇩🇪
:deciduous_tree: 🌳
:department_store: 🏬
:diamond_shape_with_a_dot_inside: 💠
:diamonds: ♦️
:disappointed: 😞
:disappointed_relieved: 😥
:dizzy: 💫
:dizzy_face: 😵
:do_not_litter: 🚯
:dog2: 🐕
:dog: 🐶
:dollar: 💵
:dolls: 🎎
:dolphin: 🐬
:door: 🚪
:doughnut: 🍩
:dragon: 🐉
:dragon_face: 🐲
:dress: 👗
:dromedary_camel: 🐪
:droplet: 💧
:dvd: 📀
:ear: 👂
:ear_of_rice: 🌾
:earth_africa: 🌍
:earth_americas: 🌎
:earth_asia: 🌏
:egg: 🥚
:eggplant: 🍆
:eight: 8
:eight_pointed_black_star: ✴️
:eight_spoked_asterisk: ✳️
:electric_plug: 🔌
:elephant: 🐘
:email: 📧
:end: 🔚
:envelope: ✉️
:es: 🇪🇸
:euro: 💶
:european_castle: 🏰
:european_post_office: 🏤
:evergreen_tree: 🌲
:exclamation: ❗
:expressionless: 😑
:eyeglasses: 👓
:eyes: 👀
:factory: 🏭
:fallen_leaf: 🍂
:family: 👪
:fast_forward: ⏩
:fax: 📠
:fearful: 😨
:feet: 🐾
:ferris_wheel: 🎡
:file_folder: 📁
:fire: 🔥
:fire_engine: 🚒
:fireworks: 🎆
:first_quarter_moon: 🌓
:first_quarter_moon_with_face: 🌛
:fish: 🐟
:fish_cake: 🍥
:fishing_pole_and_fish: 🎣
:fist: ✊
:five: 5
:flags: 🎏
:flashlight: 🔦
:floppy_disk: 💾
:flower_playing_cards: 🎴
:flushed: 😳
:foggy: 🌁
:football: 🏈
:fork_and_knife: 🍴
:fountain: ⛲
:four: 4
:four_leaf_clover: 🍀
:fr: 🇫🇷
:free: 🆓
:fried_shrimp: 🍤
:fries: 🍟
:frog: 🐸
:frowning: 😦
:fuelpump: ⛽
:full_moon: 🌕
:full_moon_with_face: 🌝
:game_die: 🎲
:gem: 💎
:gemini: ♊
:ghost: 👻
:gift: 🎁
:gift_heart: 💝
:girl: 👧
:globe_with_meridians: 🌐
:goat: 🐐
:golf: ⛳
:grapes: 🍇
:green_apple: 🍏
:green_book: 📗
:green_heart: 💚
:grey_exclamation: ❕
:grey_question: ❔
:grimacing: 😬
:grin: 😁
:grinning: 😀
:guardsman: 💂‍♂️
:guitar: 🎸
:gun: 🔫
:haircut: 💇
:hamburger: 🍔
:hammer: 🔨
:hamster: 🐹
:handbag: 👜
:hankey: 💩
:hash: #️⃣
:hatched_chick: 🐥
:hatching_chick: 🐣
:headphones: 🎧
:hear_no_evil: 🙉
:heart: ❤️
:heart_decoration: 💟
:heart_eyes: 😍
:heart_eyes_cat: 😻
:heartbeat: 💓
:hearts: ♥️
:heavy_check_mark: ✔️
:heavy_division_sign: ➗
:heavy_dollar_sign: 💲
:heavy_minus_sign:
:heavy_multiplication_x: ✖️
:heavy_plus_sign:
:helicopter: 🚁
:herb: 🌿
:hibiscus: 🌺
:high_brightness: 🔆
:high_heel: 👠
:honey_pot: 🍯
:horse: 🐴
:horse_racing: 🏇
:hospital: 🏥
:hotel: 🏨
:hotsprings: ♨️
:hourglass: ⌛
:hourglass_flowing_sand: ⏳
:house: 🏠
:house_with_garden: 🏡
:hushed: 😯
:ice_cream: 🍨
:icecream: 🍦
:id: 🆔
:ideograph_advantage: 🉐
:imp: 👿
:inbox_tray: 📥
:incoming_envelope: 📨
:information_desk_person: 💁
:information_source:
:innocent: 😇
:interrobang: ⁉️
:iphone: 📱
:it: 🇮🇹
:izakaya_lantern: 🏮
:jack_o_lantern: 🎃
:japan: 🗾
:japanese_castle: 🏯
:japanese_goblin: 👺
:japanese_ogre: 👹
:jeans: 👖
:joy: 😂
:joy_cat: 😹
:jp: 🇯🇵
:key: 🔑
:keycap_ten: 🔟
:kimono: 👘
:kiss: 💋
:kissing: 😗
:kissing_cat: 😽
:kissing_closed_eyes: 😚
:kissing_heart: 😘
:kissing_smiling_eyes: 😙
:koala: 🐨
:koko: 🈁
:kr: 🇰🇷
:large_blue_diamond: 🔷
:large_orange_diamond: 🔶
:last_quarter_moon: 🌗
:last_quarter_moon_with_face: 🌜
:laughing: 😆
:leaves: 🍃
:ledger: 📒
:left_luggage: 🛅
:left_right_arrow: ↔️
:leftwards_arrow_with_hook: ↩️
:lemon: 🍋
:leo: ♌
:leopard: 🐆
:libra: ♎
:light_rail: 🚈
:link: 🔗
:lips: 👄
:lipstick: 💄
:lock: 🔒
:lock_with_ink_pen: 🔏
:lollipop: 🍭
:loop: ➿
:loudspeaker: 📢
:love_hotel: 🏩
:love_letter: 💌
:low_brightness: 🔅
:m: Ⓜ️
:mag: 🔍
:mag_right: 🔎
:mahjong: 🀄
:mailbox: 📫
:mailbox_closed: 📪
:mailbox_with_mail: 📬
:mailbox_with_no_mail: 📭
:man: 👨
:man_with_gua_pi_mao: 👲
:man_with_turban: 👳‍♂️
:mans_shoe: 👞
:maple_leaf: 🍁
:mask: 😷
:massage: 💆
:meat_on_bone: 🍖
:mega: 📣
:melon: 🍈
:memo: 📝
:mens: 🚹
:metal: 🤘
:metro: 🚇
:microphone: 🎤
:microscope: 🔬
:milky_way: 🌌
:minibus: 🚐
:minidisc: 💽
:mobile_phone_off: 📴
:money_with_wings: 💸
:moneybag: 💰
:monkey: 🐒
:monkey_face: 🐵
:monorail: 🚝
:mortar_board: 🎓
:mount_fuji: 🗻
:mountain_bicyclist: 🚵
:mountain_cableway: 🚠
:mountain_railway: 🚞
:mouse2: 🐁
:mouse: 🐭
:movie_camera: 🎥
:moyai: 🗿
:muscle: 💪
:mushroom: 🍄
:musical_keyboard: 🎹
:musical_note: 🎵
:musical_score: 🎼
:mute: 🔇
:nail_care: 💅
:name_badge: 📛
:necktie: 👔
:negative_squared_cross_mark: ❎
:neutral_face: 😐
:new: 🆕
:new_moon: 🌑
:new_moon_with_face: 🌚
:newspaper: 📰
:ng: 🆖
:nine: 9
:no_bell: 🔕
:no_bicycles: 🚳
:no_entry: ⛔
:no_entry_sign: 🚫
:no_good: 🙅
:no_mobile_phones: 📵
:no_mouth: 😶
:no_pedestrians: 🚷
:no_smoking: 🚭
:nose: 👃
:notebook: 📓
:notebook_with_decorative_cover: 📔
:notes: 🎶
:nut_and_bolt: 🔩
:o2: 🅾️
:o: ⭕
:ocean: 🌊
:octopus: 🐙
:oden: 🍢
:office: 🏢
:ok: 🆗
:ok_hand: 👌
:ok_woman: 🙆‍♀️
:older_man: 👴
:older_woman: 👵
:on: 🔛
:oncoming_automobile: 🚘
:oncoming_bus: 🚍
:oncoming_police_car: 🚔
:oncoming_taxi: 🚖
:one: 1
:open_file_folder: 📂
:open_hands: 👐
:open_mouth: 😮
:ophiuchus: ⛎
:orange_book: 📙
:outbox_tray: 📤
:ox: 🐂
:page_facing_up: 📄
:page_with_curl: 📃
:pager: 📟
:palm_tree: 🌴
:panda_face: 🐼
:paperclip: 📎
:parking: 🅿️
:part_alternation_mark: 〽️
:partly_sunny: ⛅
:passport_control: 🛂
:paw_prints: 🐾
:peach: 🍑
:pear: 🍐
:pencil2: ✏️
:pencil: 📝
:penguin: 🐧
:pensive: 😔
:performing_arts: 🎭
:persevere: 😣
:pig2: 🐖
:pig: 🐷
:pig_nose: 🐽
:pill: 💊
:pineapple: 🍍
:pisces: ♓
:pizza: 🍕
:point_down: 👇
:point_left: 👈
:point_right: 👉
:point_up: ☝️
:point_up_2: 👆
:police_car: 🚓
:poodle: 🐩
:poop: 💩
:post_office: 🏣
:postal_horn: 📯
:postbox: 📮
:potable_water: 🚰
:pouch: 👝
:poultry_leg: 🍗
:pound: 💷
:pouting_cat: 😾
:pray: 🙏
:princess: 👸
:punch: 👊
:purple_heart: 💜
:purse: 👛
:pushpin: 📌
:put_litter_in_its_place: 🚮
:question: ❓
:rabbit2: 🐇
:rabbit: 🐰
:racehorse: 🐎
:radio: 📻
:radio_button: 🔘
:rage: 😡
:railway_car: 🚃
:rainbow: 🌈
:raised_hand: ✋
:raised_hands: 🙌
:raising_hand: 🙋
:ram: 🐏
:ramen: 🍜
:rat: 🐀
:recycle: ♻️
:red_car: 🚗
:red_circle: 🔴
:registered: ®️
:relaxed: ☺️
:relieved: 😌
:repeat: 🔁
:repeat_one: 🔂
:restroom: 🚻
:revolving_hearts: 💞
:rewind: ⏪
:ribbon: 🎀
:rice: 🍚
:rice_ball: 🍙
:rice_cracker: 🍘
:rice_scene: 🎑
:ring: 💍
:rocket: 🚀
:roller_coaster: 🎢
:rooster: 🐓
:rose: 🌹
:rotating_light: 🚨
:round_pushpin: 📍
:rowboat: 🚣
:ru: 🇷🇺
:rugby_football: 🏉
:runner: 🏃
:running_shirt_with_sash: 🎽
:sa: 🈂️
:sagittarius: ♐
:sailboat: ⛵
:sake: 🍶
:sandal: 👡
:santa: 🎅
:satellite: 📡
:satisfied: 😆
:saxophone: 🎷
:school: 🏫
:school_satchel: 🎒
:scissors: ✂️
:scorpius: ♏
:scream: 😱
:scream_cat: 🙀
:scroll: 📜
:seat: 💺
:secret: ㊙️
:see_no_evil: 🙈
:seedling: 🌱
:seven: 7
:shaved_ice: 🍧
:sheep: 🐑
:shell: 🐚
:ship: 🚢
:shirt: 👕
:shit: 💩
:shower: 🚿
:signal_strength: 📶
:six: 6
:six_pointed_star: 🔯
:ski: 🎿
:skull: 💀
:sleeping: 😴
:sleepy: 😪
:slot_machine: 🎰
:small_blue_diamond: 🔹
:small_orange_diamond: 🔸
:small_red_triangle: 🔺
:small_red_triangle_down: 🔻
:smile: 😄
:smile_cat: 😸
:smiley: 😃
:smiley_cat: 😺
:smiling_imp: 😈
:smirk: 😏
:smirk_cat: 😼
:smoking: 🚬
:snail: 🐌
:snake: 🐍
:snowboarder: 🏂
:snowflake: ❄️
:snowman: ⛄
:sob: 😭
:soccer: ⚽
:soon: 🔜
:sos: 🆘
:sound: 🔉
:space_invader: 👾
:spades: ♠️
:spaghetti: 🍝
:sparkler: 🎇
:sparkles: ✨
:sparkling_heart: 💖
:speak_no_evil: 🙊
:speaker: 🔈
:speech_balloon: 💬
:speedboat: 🚤
:star2: 🌟
:star: ⭐
:stars: 🌠
:station: 🚉
:statue_of_liberty: 🗽
:steam_locomotive: 🚂
:stew: 🍲
:straight_ruler: 📏
:strawberry: 🍓
:stuck_out_tongue: 😛
:stuck_out_tongue_closed_eyes: 😝
:stuck_out_tongue_winking_eye: 😜
:sun_with_face: 🌞
:sunflower: 🌻
:sunglasses: 😎
:sunny: ☀️
:sunrise: 🌅
:sunrise_over_mountains: 🌄
:surfer: 🏄
:sushi: 🍣
:suspension_railway: 🚟
:sweat: 😓
:sweat_drops: 💦
:sweat_smile: 😅
:sweet_potato: 🍠
:swimmer: 🏊
:symbols: 🔣
:syringe: 💉
:tada: 🎉
:tanabata_tree: 🎋
:tangerine: 🍊
:taurus: ♉
:taxi: 🚕
:tea: 🍵
:telephone: ☎️
:telephone_receiver: 📞
:telescope: 🔭
:tennis: 🎾
:tent: ⛺
:thought_balloon: 💭
:three: 3
:thumbsdown: 👎
:thumbsup: 👍
:ticket: 🎫
:tiger2: 🐅
:tiger: 🐯
:tired_face: 😫
:tm: ™️
:toilet: 🚽
:tokyo_tower: 🗼
:tomato: 🍅
:tongue: 👅
:top: 🔝
:tophat: 🎩
:tractor: 🚜
:traffic_light: 🚥
:train2: 🚆
:train: 🚋
:tram: 🚊
:triangular_flag_on_post: 🚩
:triangular_ruler: 📐
:trident: 🔱
:triumph: 😤
:trolleybus: 🚎
:trophy: 🏆
:tropical_drink: 🍹
:tropical_fish: 🐠
:truck: 🚚
:trumpet: 🎺
:tulip: 🌷
:turtle: 🐢
:tv: 📺
:twisted_rightwards_arrows: 🔀
:two: 2
:two_men_holding_hands: 👬
:two_women_holding_hands: 👭
:u5272: 🈹
:u5408: 🈴
:u55b6: 🈺
:u6307: 🈯
:u6708: 🈷️
:u6709: 🈶
:u6e80: 🈵
:u7121: 🈚
:u7533: 🈸
:u7981: 🈲
:u7a7a: 🈳
:umbrella: ☔
:unamused: 😒
:underage: 🔞
:unlock: 🔓
:up: 🆙
:us: 🇺🇸
:v: ✌️
:vertical_traffic_light: 🚦
:vhs: 📼
:vibration_mode: 📳
:video_camera: 📹
:video_game: 🎮
:violin: 🎻
:virgo: ♍
:volcano: 🌋
:vs: 🆚
:walking: 🚶
:waning_crescent_moon: 🌘
:waning_gibbous_moon: 🌖
:warning: ⚠️
:watch: ⌚
:water_buffalo: 🐃
:watermelon: 🍉
:wave: 👋
:wavy_dash: 〰️
:waxing_crescent_moon: 🌒
:waxing_gibbous_moon: 🌔
:wc: 🚾
:weary: 😩
:wedding: 💒
:whale2: 🐋
:whale: 🐳
:wheelchair: ♿
:white_check_mark: ✅
:white_circle: ⚪
:white_flower: 💮
:white_square_button: 🔳
:wind_chime: 🎐
:wine_glass: 🍷
:wink: 😉
:wolf: 🐺
:woman: 👩
:womans_clothes: 👚
:womans_hat: 👒
:womens: 🚺
:worried: 😟
:wrench: 🔧
:x: ❌
:yellow_heart: 💛
:yen: 💴
:yum: 😋
:zap: ⚡
:zero: 0
:zzz: 💤
1 :+1: 👍
2 :100: 💯
3 :1234: 🔢
4 :8ball: 🎱
5 :a: 🅰️
6 :ab: 🆎
7 :abc: 🔤
8 :abcd: 🔡
9 :accept: 🉑
10 :aerial_tramway: 🚡
11 :airplane: ✈️
12 :alarm_clock:
13 :alien: 👽
14 :ambulance: 🚑
15 :anchor:
16 :angel: 👼
17 :anger: 💢
18 :angry: 😠
19 :anguished: 😧
20 :ant: 🐜
21 :apple: 🍎
22 :aquarius:
23 :aries:
24 :arrow_backward: ◀️
25 :arrow_double_down:
26 :arrow_double_up:
27 :arrow_down: ⬇️
28 :arrow_down_small: 🔽
29 :arrow_forward: ▶️
30 :arrow_heading_down: ⤵️
31 :arrow_heading_up: ⤴️
32 :arrow_left: ⬅️
33 :arrow_lower_left: ↙️
34 :arrow_lower_right: ↘️
35 :arrow_right: ➡️
36 :arrow_right_hook: ↪️
37 :arrow_up: ⬆️
38 :arrow_up_down: ↕️
39 :arrow_up_small: 🔼
40 :arrow_upper_left: ↖️
41 :arrow_upper_right: ↗️
42 :arrows_clockwise: 🔃
43 :arrows_counterclockwise: 🔄
44 :art: 🎨
45 :articulated_lorry: 🚛
46 :astonished: 😲
47 :atm: 🏧
48 :b: 🅱️
49 :baby: 👶
50 :baby_bottle: 🍼
51 :baby_chick: 🐤
52 :baby_symbol: 🚼
53 :baggage_claim: 🛄
54 :balloon: 🎈
55 :ballot_box_with_check: ☑️
56 :bamboo: 🎍
57 :banana: 🍌
58 :bangbang: ‼️
59 :bank: 🏦
60 :bar_chart: 📊
61 :barber: 💈
62 :baseball:
63 :basketball: 🏀
64 :bath: 🛀
65 :bathtub: 🛁
66 :battery: 🔋
67 :bear: 🐻
68 :beer: 🍺
69 :beers: 🍻
70 :beetle: 🪲
71 :beginner: 🔰
72 :bell: 🔔
73 :bento: 🍱
74 :bicyclist: 🚴
75 :bike: 🚲
76 :bikini: 👙
77 :bird: 🐦
78 :birthday: 🎂
79 :black_circle:
80 :black_joker: 🃏
81 :black_nib: ✒️
82 :black_square_button: 🔲
83 :blossom: 🌼
84 :blowfish: 🐡
85 :blue_book: 📘
86 :blue_car: 🚙
87 :blue_heart: 💙
88 :blush: 😊
89 :boar: 🐗
90 :boat:
91 :bomb: 💣
92 :book: 📖
93 :bookmark: 🔖
94 :bookmark_tabs: 📑
95 :books: 📚
96 :boom: 💥
97 :boot: 👢
98 :bouquet: 💐
99 :bow: 🙇
100 :bowling: 🎳
101 :boy: 👦
102 :bread: 🍞
103 :bride_with_veil: 👰‍♀️
104 :bridge_at_night: 🌉
105 :briefcase: 💼
106 :broken_heart: 💔
107 :bug: 🐛
108 :bulb: 💡
109 :bullettrain_front: 🚅
110 :bullettrain_side: 🚄
111 :bus: 🚌
112 :busstop: 🚏
113 :bust_in_silhouette: 👤
114 :busts_in_silhouette: 👥
115 :cactus: 🌵
116 :cake: 🍰
117 :calendar: 📆
118 :calling: 📲
119 :camel: 🐫
120 :camera: 📷
121 :cancer:
122 :candy: 🍬
123 :capital_abcd: 🔠
124 :capricorn:
125 :car: 🚗
126 :card_index: 📇
127 :carousel_horse: 🎠
128 :cat2: 🐈
129 :cat: 🐱
130 :cd: 💿
131 :chart: 💹
132 :chart_with_downwards_trend: 📉
133 :chart_with_upwards_trend: 📈
134 :checkered_flag: 🏁
135 :cherries: 🍒
136 :cherry_blossom: 🌸
137 :chestnut: 🌰
138 :chicken: 🐔
139 :children_crossing: 🚸
140 :chocolate_bar: 🍫
141 :christmas_tree: 🎄
142 :church:
143 :cinema: 🎦
144 :circus_tent: 🎪
145 :city_sunrise: 🌇
146 :city_sunset: 🌆
147 :cl: 🆑
148 :clap: 👏
149 :clapper: 🎬
150 :clipboard: 📋
151 :clock1030: 🕥
152 :clock10: 🕙
153 :clock1130: 🕦
154 :clock11: 🕚
155 :clock1230: 🕧
156 :clock12: 🕛
157 :clock130: 🕜
158 :clock1: 🕐
159 :clock230: 🕝
160 :clock2: 🕑
161 :clock330: 🕞
162 :clock3: 🕒
163 :clock430: 🕟
164 :clock4: 🕓
165 :clock530: 🕠
166 :clock5: 🕔
167 :clock630: 🕡
168 :clock6: 🕕
169 :clock730: 🕢
170 :clock7: 🕖
171 :clock830: 🕣
172 :clock8: 🕗
173 :clock930: 🕤
174 :clock9: 🕘
175 :closed_book: 📕
176 :closed_lock_with_key: 🔐
177 :closed_umbrella: 🌂
178 :cloud: ☁️
179 :clubs: ♣️
180 :cn: 🇨🇳
181 :cocktail: 🍸
182 :coffee:
183 :cold_sweat: 😰
184 :computer: 💻
185 :confetti_ball: 🎊
186 :confounded: 😖
187 :confused: 😕
188 :congratulations: ㊗️
189 :construction: 🚧
190 :construction_worker: 👷
191 :convenience_store: 🏪
192 :cookie: 🍪
193 :cool: 🆒
194 :cop: 👮
195 :copyright: ©️
196 :corn: 🌽
197 :couple: 👫
198 :couple_with_heart: 💑
199 :couplekiss: 💏
200 :cow2: 🐄
201 :cow: 🐮
202 :credit_card: 💳
203 :crocodile: 🐊
204 :crossed_flags: 🎌
205 :crown: 👑
206 :cry: 😢
207 :crying_cat_face: 😿
208 :crystal_ball: 🔮
209 :cupid: 💘
210 :curly_loop:
211 :currency_exchange: 💱
212 :curry: 🍛
213 :custard: 🍮
214 :customs: 🛃
215 :cyclone: 🌀
216 :dancer: 💃
217 :dancers: 👯
218 :dango: 🍡
219 :dart: 🎯
220 :dash: 💨
221 :date: 📅
222 :de: 🇩🇪
223 :deciduous_tree: 🌳
224 :department_store: 🏬
225 :diamond_shape_with_a_dot_inside: 💠
226 :diamonds: ♦️
227 :disappointed: 😞
228 :disappointed_relieved: 😥
229 :dizzy: 💫
230 :dizzy_face: 😵
231 :do_not_litter: 🚯
232 :dog2: 🐕
233 :dog: 🐶
234 :dollar: 💵
235 :dolls: 🎎
236 :dolphin: 🐬
237 :door: 🚪
238 :doughnut: 🍩
239 :dragon: 🐉
240 :dragon_face: 🐲
241 :dress: 👗
242 :dromedary_camel: 🐪
243 :droplet: 💧
244 :dvd: 📀
245 :ear: 👂
246 :ear_of_rice: 🌾
247 :earth_africa: 🌍
248 :earth_americas: 🌎
249 :earth_asia: 🌏
250 :egg: 🥚
251 :eggplant: 🍆
252 :eight: 8️⃣
253 :eight_pointed_black_star: ✴️
254 :eight_spoked_asterisk: ✳️
255 :electric_plug: 🔌
256 :elephant: 🐘
257 :email: 📧
258 :end: 🔚
259 :envelope: ✉️
260 :es: 🇪🇸
261 :euro: 💶
262 :european_castle: 🏰
263 :european_post_office: 🏤
264 :evergreen_tree: 🌲
265 :exclamation:
266 :expressionless: 😑
267 :eyeglasses: 👓
268 :eyes: 👀
269 :factory: 🏭
270 :fallen_leaf: 🍂
271 :family: 👪
272 :fast_forward:
273 :fax: 📠
274 :fearful: 😨
275 :feet: 🐾
276 :ferris_wheel: 🎡
277 :file_folder: 📁
278 :fire: 🔥
279 :fire_engine: 🚒
280 :fireworks: 🎆
281 :first_quarter_moon: 🌓
282 :first_quarter_moon_with_face: 🌛
283 :fish: 🐟
284 :fish_cake: 🍥
285 :fishing_pole_and_fish: 🎣
286 :fist:
287 :five: 5️⃣
288 :flags: 🎏
289 :flashlight: 🔦
290 :floppy_disk: 💾
291 :flower_playing_cards: 🎴
292 :flushed: 😳
293 :foggy: 🌁
294 :football: 🏈
295 :fork_and_knife: 🍴
296 :fountain:
297 :four: 4️⃣
298 :four_leaf_clover: 🍀
299 :fr: 🇫🇷
300 :free: 🆓
301 :fried_shrimp: 🍤
302 :fries: 🍟
303 :frog: 🐸
304 :frowning: 😦
305 :fuelpump:
306 :full_moon: 🌕
307 :full_moon_with_face: 🌝
308 :game_die: 🎲
309 :gem: 💎
310 :gemini:
311 :ghost: 👻
312 :gift: 🎁
313 :gift_heart: 💝
314 :girl: 👧
315 :globe_with_meridians: 🌐
316 :goat: 🐐
317 :golf:
318 :grapes: 🍇
319 :green_apple: 🍏
320 :green_book: 📗
321 :green_heart: 💚
322 :grey_exclamation:
323 :grey_question:
324 :grimacing: 😬
325 :grin: 😁
326 :grinning: 😀
327 :guardsman: 💂‍♂️
328 :guitar: 🎸
329 :gun: 🔫
330 :haircut: 💇
331 :hamburger: 🍔
332 :hammer: 🔨
333 :hamster: 🐹
334 :handbag: 👜
335 :hankey: 💩
336 :hash: #️⃣
337 :hatched_chick: 🐥
338 :hatching_chick: 🐣
339 :headphones: 🎧
340 :hear_no_evil: 🙉
341 :heart: ❤️
342 :heart_decoration: 💟
343 :heart_eyes: 😍
344 :heart_eyes_cat: 😻
345 :heartbeat: 💓
346 :hearts: ♥️
347 :heavy_check_mark: ✔️
348 :heavy_division_sign:
349 :heavy_dollar_sign: 💲
350 :heavy_minus_sign:
351 :heavy_multiplication_x: ✖️
352 :heavy_plus_sign:
353 :helicopter: 🚁
354 :herb: 🌿
355 :hibiscus: 🌺
356 :high_brightness: 🔆
357 :high_heel: 👠
358 :honey_pot: 🍯
359 :horse: 🐴
360 :horse_racing: 🏇
361 :hospital: 🏥
362 :hotel: 🏨
363 :hotsprings: ♨️
364 :hourglass:
365 :hourglass_flowing_sand:
366 :house: 🏠
367 :house_with_garden: 🏡
368 :hushed: 😯
369 :ice_cream: 🍨
370 :icecream: 🍦
371 :id: 🆔
372 :ideograph_advantage: 🉐
373 :imp: 👿
374 :inbox_tray: 📥
375 :incoming_envelope: 📨
376 :information_desk_person: 💁
377 :information_source: ℹ️
378 :innocent: 😇
379 :interrobang: ⁉️
380 :iphone: 📱
381 :it: 🇮🇹
382 :izakaya_lantern: 🏮
383 :jack_o_lantern: 🎃
384 :japan: 🗾
385 :japanese_castle: 🏯
386 :japanese_goblin: 👺
387 :japanese_ogre: 👹
388 :jeans: 👖
389 :joy: 😂
390 :joy_cat: 😹
391 :jp: 🇯🇵
392 :key: 🔑
393 :keycap_ten: 🔟
394 :kimono: 👘
395 :kiss: 💋
396 :kissing: 😗
397 :kissing_cat: 😽
398 :kissing_closed_eyes: 😚
399 :kissing_heart: 😘
400 :kissing_smiling_eyes: 😙
401 :koala: 🐨
402 :koko: 🈁
403 :kr: 🇰🇷
404 :large_blue_diamond: 🔷
405 :large_orange_diamond: 🔶
406 :last_quarter_moon: 🌗
407 :last_quarter_moon_with_face: 🌜
408 :laughing: 😆
409 :leaves: 🍃
410 :ledger: 📒
411 :left_luggage: 🛅
412 :left_right_arrow: ↔️
413 :leftwards_arrow_with_hook: ↩️
414 :lemon: 🍋
415 :leo:
416 :leopard: 🐆
417 :libra:
418 :light_rail: 🚈
419 :link: 🔗
420 :lips: 👄
421 :lipstick: 💄
422 :lock: 🔒
423 :lock_with_ink_pen: 🔏
424 :lollipop: 🍭
425 :loop:
426 :loudspeaker: 📢
427 :love_hotel: 🏩
428 :love_letter: 💌
429 :low_brightness: 🔅
430 :m: Ⓜ️
431 :mag: 🔍
432 :mag_right: 🔎
433 :mahjong: 🀄
434 :mailbox: 📫
435 :mailbox_closed: 📪
436 :mailbox_with_mail: 📬
437 :mailbox_with_no_mail: 📭
438 :man: 👨
439 :man_with_gua_pi_mao: 👲
440 :man_with_turban: 👳‍♂️
441 :mans_shoe: 👞
442 :maple_leaf: 🍁
443 :mask: 😷
444 :massage: 💆
445 :meat_on_bone: 🍖
446 :mega: 📣
447 :melon: 🍈
448 :memo: 📝
449 :mens: 🚹
450 :metal: 🤘
451 :metro: 🚇
452 :microphone: 🎤
453 :microscope: 🔬
454 :milky_way: 🌌
455 :minibus: 🚐
456 :minidisc: 💽
457 :mobile_phone_off: 📴
458 :money_with_wings: 💸
459 :moneybag: 💰
460 :monkey: 🐒
461 :monkey_face: 🐵
462 :monorail: 🚝
463 :mortar_board: 🎓
464 :mount_fuji: 🗻
465 :mountain_bicyclist: 🚵
466 :mountain_cableway: 🚠
467 :mountain_railway: 🚞
468 :mouse2: 🐁
469 :mouse: 🐭
470 :movie_camera: 🎥
471 :moyai: 🗿
472 :muscle: 💪
473 :mushroom: 🍄
474 :musical_keyboard: 🎹
475 :musical_note: 🎵
476 :musical_score: 🎼
477 :mute: 🔇
478 :nail_care: 💅
479 :name_badge: 📛
480 :necktie: 👔
481 :negative_squared_cross_mark:
482 :neutral_face: 😐
483 :new: 🆕
484 :new_moon: 🌑
485 :new_moon_with_face: 🌚
486 :newspaper: 📰
487 :ng: 🆖
488 :nine: 9️⃣
489 :no_bell: 🔕
490 :no_bicycles: 🚳
491 :no_entry:
492 :no_entry_sign: 🚫
493 :no_good: 🙅
494 :no_mobile_phones: 📵
495 :no_mouth: 😶
496 :no_pedestrians: 🚷
497 :no_smoking: 🚭
498 :nose: 👃
499 :notebook: 📓
500 :notebook_with_decorative_cover: 📔
501 :notes: 🎶
502 :nut_and_bolt: 🔩
503 :o2: 🅾️
504 :o:
505 :ocean: 🌊
506 :octopus: 🐙
507 :oden: 🍢
508 :office: 🏢
509 :ok: 🆗
510 :ok_hand: 👌
511 :ok_woman: 🙆‍♀️
512 :older_man: 👴
513 :older_woman: 👵
514 :on: 🔛
515 :oncoming_automobile: 🚘
516 :oncoming_bus: 🚍
517 :oncoming_police_car: 🚔
518 :oncoming_taxi: 🚖
519 :one: 1️⃣
520 :open_file_folder: 📂
521 :open_hands: 👐
522 :open_mouth: 😮
523 :ophiuchus:
524 :orange_book: 📙
525 :outbox_tray: 📤
526 :ox: 🐂
527 :page_facing_up: 📄
528 :page_with_curl: 📃
529 :pager: 📟
530 :palm_tree: 🌴
531 :panda_face: 🐼
532 :paperclip: 📎
533 :parking: 🅿️
534 :part_alternation_mark: 〽️
535 :partly_sunny:
536 :passport_control: 🛂
537 :paw_prints: 🐾
538 :peach: 🍑
539 :pear: 🍐
540 :pencil2: ✏️
541 :pencil: 📝
542 :penguin: 🐧
543 :pensive: 😔
544 :performing_arts: 🎭
545 :persevere: 😣
546 :pig2: 🐖
547 :pig: 🐷
548 :pig_nose: 🐽
549 :pill: 💊
550 :pineapple: 🍍
551 :pisces:
552 :pizza: 🍕
553 :point_down: 👇
554 :point_left: 👈
555 :point_right: 👉
556 :point_up: ☝️
557 :point_up_2: 👆
558 :police_car: 🚓
559 :poodle: 🐩
560 :poop: 💩
561 :post_office: 🏣
562 :postal_horn: 📯
563 :postbox: 📮
564 :potable_water: 🚰
565 :pouch: 👝
566 :poultry_leg: 🍗
567 :pound: 💷
568 :pouting_cat: 😾
569 :pray: 🙏
570 :princess: 👸
571 :punch: 👊
572 :purple_heart: 💜
573 :purse: 👛
574 :pushpin: 📌
575 :put_litter_in_its_place: 🚮
576 :question:
577 :rabbit2: 🐇
578 :rabbit: 🐰
579 :racehorse: 🐎
580 :radio: 📻
581 :radio_button: 🔘
582 :rage: 😡
583 :railway_car: 🚃
584 :rainbow: 🌈
585 :raised_hand:
586 :raised_hands: 🙌
587 :raising_hand: 🙋
588 :ram: 🐏
589 :ramen: 🍜
590 :rat: 🐀
591 :recycle: ♻️
592 :red_car: 🚗
593 :red_circle: 🔴
594 :registered: ®️
595 :relaxed: ☺️
596 :relieved: 😌
597 :repeat: 🔁
598 :repeat_one: 🔂
599 :restroom: 🚻
600 :revolving_hearts: 💞
601 :rewind:
602 :ribbon: 🎀
603 :rice: 🍚
604 :rice_ball: 🍙
605 :rice_cracker: 🍘
606 :rice_scene: 🎑
607 :ring: 💍
608 :rocket: 🚀
609 :roller_coaster: 🎢
610 :rooster: 🐓
611 :rose: 🌹
612 :rotating_light: 🚨
613 :round_pushpin: 📍
614 :rowboat: 🚣
615 :ru: 🇷🇺
616 :rugby_football: 🏉
617 :runner: 🏃
618 :running_shirt_with_sash: 🎽
619 :sa: 🈂️
620 :sagittarius:
621 :sailboat:
622 :sake: 🍶
623 :sandal: 👡
624 :santa: 🎅
625 :satellite: 📡
626 :satisfied: 😆
627 :saxophone: 🎷
628 :school: 🏫
629 :school_satchel: 🎒
630 :scissors: ✂️
631 :scorpius:
632 :scream: 😱
633 :scream_cat: 🙀
634 :scroll: 📜
635 :seat: 💺
636 :secret: ㊙️
637 :see_no_evil: 🙈
638 :seedling: 🌱
639 :seven: 7️⃣
640 :shaved_ice: 🍧
641 :sheep: 🐑
642 :shell: 🐚
643 :ship: 🚢
644 :shirt: 👕
645 :shit: 💩
646 :shower: 🚿
647 :signal_strength: 📶
648 :six: 6️⃣
649 :six_pointed_star: 🔯
650 :ski: 🎿
651 :skull: 💀
652 :sleeping: 😴
653 :sleepy: 😪
654 :slot_machine: 🎰
655 :small_blue_diamond: 🔹
656 :small_orange_diamond: 🔸
657 :small_red_triangle: 🔺
658 :small_red_triangle_down: 🔻
659 :smile: 😄
660 :smile_cat: 😸
661 :smiley: 😃
662 :smiley_cat: 😺
663 :smiling_imp: 😈
664 :smirk: 😏
665 :smirk_cat: 😼
666 :smoking: 🚬
667 :snail: 🐌
668 :snake: 🐍
669 :snowboarder: 🏂
670 :snowflake: ❄️
671 :snowman:
672 :sob: 😭
673 :soccer:
674 :soon: 🔜
675 :sos: 🆘
676 :sound: 🔉
677 :space_invader: 👾
678 :spades: ♠️
679 :spaghetti: 🍝
680 :sparkler: 🎇
681 :sparkles:
682 :sparkling_heart: 💖
683 :speak_no_evil: 🙊
684 :speaker: 🔈
685 :speech_balloon: 💬
686 :speedboat: 🚤
687 :star2: 🌟
688 :star:
689 :stars: 🌠
690 :station: 🚉
691 :statue_of_liberty: 🗽
692 :steam_locomotive: 🚂
693 :stew: 🍲
694 :straight_ruler: 📏
695 :strawberry: 🍓
696 :stuck_out_tongue: 😛
697 :stuck_out_tongue_closed_eyes: 😝
698 :stuck_out_tongue_winking_eye: 😜
699 :sun_with_face: 🌞
700 :sunflower: 🌻
701 :sunglasses: 😎
702 :sunny: ☀️
703 :sunrise: 🌅
704 :sunrise_over_mountains: 🌄
705 :surfer: 🏄
706 :sushi: 🍣
707 :suspension_railway: 🚟
708 :sweat: 😓
709 :sweat_drops: 💦
710 :sweat_smile: 😅
711 :sweet_potato: 🍠
712 :swimmer: 🏊
713 :symbols: 🔣
714 :syringe: 💉
715 :tada: 🎉
716 :tanabata_tree: 🎋
717 :tangerine: 🍊
718 :taurus:
719 :taxi: 🚕
720 :tea: 🍵
721 :telephone: ☎️
722 :telephone_receiver: 📞
723 :telescope: 🔭
724 :tennis: 🎾
725 :tent:
726 :thought_balloon: 💭
727 :three: 3️⃣
728 :thumbsdown: 👎
729 :thumbsup: 👍
730 :ticket: 🎫
731 :tiger2: 🐅
732 :tiger: 🐯
733 :tired_face: 😫
734 :tm: ™️
735 :toilet: 🚽
736 :tokyo_tower: 🗼
737 :tomato: 🍅
738 :tongue: 👅
739 :top: 🔝
740 :tophat: 🎩
741 :tractor: 🚜
742 :traffic_light: 🚥
743 :train2: 🚆
744 :train: 🚋
745 :tram: 🚊
746 :triangular_flag_on_post: 🚩
747 :triangular_ruler: 📐
748 :trident: 🔱
749 :triumph: 😤
750 :trolleybus: 🚎
751 :trophy: 🏆
752 :tropical_drink: 🍹
753 :tropical_fish: 🐠
754 :truck: 🚚
755 :trumpet: 🎺
756 :tulip: 🌷
757 :turtle: 🐢
758 :tv: 📺
759 :twisted_rightwards_arrows: 🔀
760 :two: 2️⃣
761 :two_men_holding_hands: 👬
762 :two_women_holding_hands: 👭
763 :u5272: 🈹
764 :u5408: 🈴
765 :u55b6: 🈺
766 :u6307: 🈯
767 :u6708: 🈷️
768 :u6709: 🈶
769 :u6e80: 🈵
770 :u7121: 🈚
771 :u7533: 🈸
772 :u7981: 🈲
773 :u7a7a: 🈳
774 :umbrella:
775 :unamused: 😒
776 :underage: 🔞
777 :unlock: 🔓
778 :up: 🆙
779 :us: 🇺🇸
780 :v: ✌️
781 :vertical_traffic_light: 🚦
782 :vhs: 📼
783 :vibration_mode: 📳
784 :video_camera: 📹
785 :video_game: 🎮
786 :violin: 🎻
787 :virgo:
788 :volcano: 🌋
789 :vs: 🆚
790 :walking: 🚶
791 :waning_crescent_moon: 🌘
792 :waning_gibbous_moon: 🌖
793 :warning: ⚠️
794 :watch:
795 :water_buffalo: 🐃
796 :watermelon: 🍉
797 :wave: 👋
798 :wavy_dash: 〰️
799 :waxing_crescent_moon: 🌒
800 :waxing_gibbous_moon: 🌔
801 :wc: 🚾
802 :weary: 😩
803 :wedding: 💒
804 :whale2: 🐋
805 :whale: 🐳
806 :wheelchair:
807 :white_check_mark:
808 :white_circle:
809 :white_flower: 💮
810 :white_square_button: 🔳
811 :wind_chime: 🎐
812 :wine_glass: 🍷
813 :wink: 😉
814 :wolf: 🐺
815 :woman: 👩
816 :womans_clothes: 👚
817 :womans_hat: 👒
818 :womens: 🚺
819 :worried: 😟
820 :wrench: 🔧
821 :x:
822 :yellow_heart: 💛
823 :yen: 💴
824 :yum: 😋
825 :zap:
826 :zero: 0️⃣
827 :zzz: 💤

View File

@@ -3,6 +3,15 @@ function title_from_name(name) {
gsub(/-/, " ", name) gsub(/-/, " ", name)
return name return name
} }
function url_encode_path(path) {
gsub(/%/, "%25", path)
gsub(/ /, "%20", path)
gsub(/#/, "%23", path)
gsub(/\?/, "%3F", path)
gsub(/"/, "%22", path)
gsub(/'/, "%27", path)
return path
}
function get_title(path, default_title, full_path, line, title, in_fm) { function get_title(path, default_title, full_path, line, title, in_fm) {
full_path = src "/" path full_path = src "/" path
@@ -172,7 +181,7 @@ END {
continue continue
} }
printf "<li><a href=\"/%sindex.html\">%s</a><ul>\n", dir_path, get_title(this_d, title_from_name(parts[i])) printf "<li><a href=\"/%sindex.html\">%s</a><ul>\n", url_encode_path(dir_path), get_title(this_d, title_from_name(parts[i]))
opened_levels[++depth] = i opened_levels[++depth] = i
} }
@@ -183,7 +192,7 @@ END {
is_single = (single_file_index == "true" && md_count[curr_dir] == 1 && !has_index[curr_dir]) is_single = (single_file_index == "true" && md_count[curr_dir] == 1 && !has_index[curr_dir])
if (parts[n] != "index.md" && !is_single) { if (parts[n] != "index.md" && !is_single) {
path = "/" rel path = url_encode_path("/" rel)
gsub(/\.md$/, ".html", path) gsub(/\.md$/, ".html", path)
printf "<li><a href=\"%s\">%s</a></li>\n", path, get_title(rel, title_from_name(parts[n])) printf "<li><a href=\"%s\">%s</a></li>\n", path, get_title(rel, title_from_name(parts[n]))
} }

View File

@@ -13,6 +13,31 @@ function strip_markdown(s) {
gsub(/-+$/, "", s) gsub(/-+$/, "", s)
return s return s
} }
function unique_id(raw_id, candidate) {
candidate = raw_id
if (candidate == "") candidate = "section"
if (!(candidate in seen_ids)) {
seen_ids[candidate] = 1
return candidate
}
seen_ids[candidate]++
return candidate "-" seen_ids[candidate]
}
function has_inline_link(line) {
return (index(line, "<a ") > 0 || index(line, "<a\t") > 0)
}
function print_heading(tag, line, id) {
id = unique_id(strip_markdown(line))
if (enable_header_links == "true") {
if (has_inline_link(line)) {
print "<" tag " id=\"" id "\">" line " <a href=\"#" id "\" class=\"header-anchor\" aria-label=\"Link to this section\">#</a></" tag ">"
} else {
print "<" tag " id=\"" id "\"><a href=\"#" id "\" class=\"header-link\">" line "<span class=\"header-anchor\" aria-hidden=\"true\">#</span></a></" tag ">"
}
} else {
print "<" tag " id=\"" id "\">" line "</" tag ">"
}
}
function print_header(line) { function print_header(line) {
tag = "" tag = ""
if (line ~ /^# /) { tag = "h1"; sub(/^# /, "", line) } if (line ~ /^# /) { tag = "h1"; sub(/^# /, "", line) }
@@ -23,12 +48,7 @@ function print_header(line) {
else if (line ~ /^###### /) { tag = "h6"; sub(/^###### /, "", line) } else if (line ~ /^###### /) { tag = "h6"; sub(/^###### /, "", line) }
if (tag != "") { if (tag != "") {
id = strip_markdown(line) print_heading(tag, line)
if (enable_header_links == "true") {
print "<" tag " id=\"" id "\"><a href=\"#" id "\" class=\"header-anchor\">" line "</a></" tag ">"
} else {
print "<" tag " id=\"" id "\">" line "</" tag ">"
}
} else { } else {
print line print line
} }
@@ -52,7 +72,7 @@ BEGIN {
if ($0 ~ /^=+$/) { if ($0 ~ /^=+$/) {
if (has_prev && prev != "" && prev !~ /^<[a-z]/) { if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h1 id=\"" strip_markdown(prev) "\">" prev "</h1>" print_heading("h1", prev)
has_prev = 0 has_prev = 0
} else { } else {
if (has_prev) print_header(prev) if (has_prev) print_header(prev)
@@ -61,7 +81,7 @@ BEGIN {
} }
} else if ($0 ~ /^-+$/) { } else if ($0 ~ /^-+$/) {
if (has_prev && prev != "" && prev !~ /^<[a-z]/) { if (has_prev && prev != "" && prev !~ /^<[a-z]/) {
print "<h2 id=\"" strip_markdown(prev) "\">" prev "</h2>" print_heading("h2", prev)
has_prev = 0 has_prev = 0
} else { } else {
if (has_prev) print_header(prev) if (has_prev) print_header(prev)

View File

@@ -375,6 +375,13 @@ function rewrite_img_tags(line, out, rest, tag, src, alt, force_inline_tag, e
} else if (is_image_ext(ext_of(src)) && force_inline_tag == "") { } else if (is_image_ext(ext_of(src)) && force_inline_tag == "") {
# Preserve hand-written <img> attributes (style/class/etc) for normal images. # Preserve hand-written <img> attributes (style/class/etc) for normal images.
repl = tag repl = tag
} else if (force_inline_tag != "" && !is_global_url(src) && is_inline_text_ext(ext_of(src))) {
repl = render_code_include(src, 1)
if (repl != "") {
repl = "<pre><code>" repl "</code></pre>"
} else {
repl = render_embed(src, alt, (alt != ""), 1)
}
} else { } else {
repl = render_embed(src, alt, (alt != ""), (force_inline_tag != "")) repl = render_embed(src, alt, (alt != ""), (force_inline_tag != ""))
} }
@@ -399,7 +406,12 @@ function rewrite_double_bang_with_parens(line, out, rest, token, inside, src,
src = substr(inside, sep + 2) src = substr(inside, sep + 2)
sub(/\)$/, "", src) sub(/\)$/, "", src)
repl = render_embed(src, alt, (alt != ""), 1) repl = render_code_include(src, 1)
if (repl != "") {
repl = "<pre><code>" repl "</code></pre>"
} else {
repl = render_embed(src, alt, (alt != ""), 1)
}
out = out pre repl out = out pre repl
rest = post rest = post
} }
@@ -416,7 +428,12 @@ function rewrite_double_bang_bare(line, out, rest, token, src, pre, post, rep
src = token src = token
sub(/^!!\[/, "", src) sub(/^!!\[/, "", src)
sub(/\]$/, "", src) sub(/\]$/, "", src)
repl = render_embed(src, "", 0, 1) repl = render_code_include(src, 1)
if (repl != "") {
repl = "<pre><code>" repl "</code></pre>"
} else {
repl = render_embed(src, "", 0, 1)
}
out = out pre repl out = out pre repl
rest = post rest = post
} }
@@ -576,6 +593,75 @@ function restore_plain_markers(line) {
return line return line
} }
function break_code_double_bang(line, out, rest, pstart, pend, code_content, token, pre, post, inside, sep, src, alt, repl) {
out = ""
rest = line
while (1) {
pstart = index(rest, "<code>")
if (pstart == 0) {
out = out rest
break
}
out = out substr(rest, 1, pstart - 1)
rest = substr(rest, pstart)
pend = index(substr(rest, 7), "</code>")
if (pend == 0) {
out = out rest
break
}
pend = pend + 6
code_content = substr(rest, 7, pend - 7)
rest = substr(rest, pend + 7)
if (match(code_content, /!!\[[^\]]*\]\([^)]*\)/)) {
token = substr(code_content, RSTART, RLENGTH)
pre = substr(code_content, 1, RSTART - 1)
post = substr(code_content, RSTART + RLENGTH)
inside = token
sub(/^!!\[/, "", inside)
sep = index(inside, "](")
alt = substr(inside, 1, sep - 1)
src = substr(inside, sep + 2)
sub(/\)$/, "", src)
repl = render_code_include(src, 1)
if (repl != "") {
repl = "<pre><code>" repl "</code></pre>"
} else {
repl = render_embed(src, alt, (alt != ""), 1)
}
if (repl == "") {
out = out "<code>" code_content "</code>"
} else {
if (pre != "") out = out "<code>" pre "</code>"
out = out repl
if (post != "") out = out "<code>" post "</code>"
}
} else if (match(code_content, /!!\[[^\]]+\]/)) {
token = substr(code_content, RSTART, RLENGTH)
pre = substr(code_content, 1, RSTART - 1)
post = substr(code_content, RSTART + RLENGTH)
src = token
sub(/^!!\[/, "", src)
sub(/\]$/, "", src)
repl = render_code_include(src, 1)
if (repl != "") {
repl = "<pre><code>" repl "</code></pre>"
} else {
repl = render_embed(src, "", 0, 1)
}
if (repl == "") {
out = out "<code>" code_content "</code>"
} else {
if (pre != "") out = out "<code>" pre "</code>"
out = out repl
if (post != "") out = out "<code>" post "</code>"
}
} else {
out = out "<code>" code_content "</code>"
}
}
return out
}
BEGIN { BEGIN {
input_dir = dirname_of(input_file) input_dir = dirname_of(input_file)
in_pre_code = 0 in_pre_code = 0
@@ -601,6 +687,9 @@ BEGIN {
line = apply_td_vertical_align(line) line = apply_td_vertical_align(line)
line = restore_plain_markers(line) line = restore_plain_markers(line)
if (!(in_pre_code || start_pre)) {
line = break_code_double_bang(line)
}
print line print line
if (start_pre && !end_pre) { if (start_pre && !end_pre) {

View File

@@ -67,6 +67,17 @@ function mask(s, t) {
if (plen >= 2 && substr(content, 1, 1) == " " && substr(content, length(content), 1) == " ") { if (plen >= 2 && substr(content, 1, 1) == " " && substr(content, length(content), 1) == " ") {
content = substr(content, 2, length(content) - 2) content = substr(content, 2, length(content) - 2)
} }
if (content ~ /!!\[/) {
_rb_test = content
gsub(/!!\[[^\]]*\]\([^)]*\)/, "", _rb_test)
gsub(/!!\[[^\]]+\]/, "", _rb_test)
gsub(/[[:space:]]+/, "", _rb_test)
if (_rb_test == "") {
out = out content
p = pstart + plen + mpos + plen - 1
continue
}
}
out = out "<code>" mask(content) "</code>" out = out "<code>" mask(content) "</code>"
p = pstart + plen + mpos + plen - 1 p = pstart + plen + mpos + plen - 1
} else { } else {

View File

@@ -50,7 +50,7 @@ END {
in_pre = 0 in_pre = 0
i = 1 i = 1
while (i <= count) { while (i <= count) {
if (lines[i] ~ /^<pre><code>/) { if (lines[i] ~ /^<pre><code/) {
in_pre = 1 in_pre = 1
print lines[i] print lines[i]
i++ i++

View File

@@ -16,6 +16,7 @@ BEGIN {
style_path = ENVIRON["AWK_STYLE_PATH"] style_path = ENVIRON["AWK_STYLE_PATH"]
head_extra = ENVIRON["AWK_HEAD_EXTRA"] head_extra = ENVIRON["AWK_HEAD_EXTRA"]
header_brand = ENVIRON["AWK_HEADER_BRAND"] header_brand = ENVIRON["AWK_HEADER_BRAND"]
header_search = ENVIRON["AWK_HEADER_SEARCH"]
lang = ENVIRON["AWK_LANG"] lang = ENVIRON["AWK_LANG"]
version = ENVIRON["AWK_VERSION"] version = ENVIRON["AWK_VERSION"]
content_warning = ENVIRON["AWK_CONTENT_WARNING"] content_warning = ENVIRON["AWK_CONTENT_WARNING"]
@@ -33,6 +34,7 @@ BEGIN {
line = replace_all(line, "{{CSS}}", style_path) line = replace_all(line, "{{CSS}}", style_path)
line = replace_all(line, "{{HEAD_EXTRA}}", head_extra) line = replace_all(line, "{{HEAD_EXTRA}}", head_extra)
line = replace_all(line, "{{HEADER_BRAND}}", header_brand) line = replace_all(line, "{{HEADER_BRAND}}", header_brand)
line = replace_all(line, "{{HEADER_SEARCH}}", header_search)
line = replace_all(line, "{{VERSION}}", version) line = replace_all(line, "{{VERSION}}", version)
pos = index(line, "{{CONTENT}}") pos = index(line, "{{CONTENT}}")

193
kewt.sh
View File

@@ -5,17 +5,34 @@ die() {
exit 1 exit 1
} }
make_temp_dir() {
_temp_base="${TMPDIR:-/tmp}/kewt_run.$$"
_temp_try=0
while [ "$_temp_try" -lt 1000 ]; do
_temp_path="${_temp_base}.${_temp_try}"
if (umask 077 && mkdir "$_temp_path") 2>/dev/null; then
printf '%s\n' "$_temp_path"
return 0
fi
_temp_try=$((_temp_try + 1))
done
return 1
}
script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd) script_dir=$(CDPATH="" cd -- "$(dirname -- "$0")" && pwd)
awk_dir="$script_dir/awk" awk_dir="$script_dir/awk"
KEWT_TMPDIR=$(mktemp -d "/tmp/kewt_run.XXXXXX") KEWT_TMPDIR=$(make_temp_dir) || die "Could not create temporary directory."
trap 'rm -rf "$KEWT_TMPDIR"' EXIT trap 'rm -rf "$KEWT_TMPDIR"' EXIT
trap 'exit 0' HUP INT TERM trap 'exit 0' HUP INT TERM
. "$script_dir/lib/config.sh" . "$script_dir/lib/config.sh"
. "$script_dir/lib/metadata.sh"
. "$script_dir/lib/manifest.sh"
. "$script_dir/lib/commands.sh" . "$script_dir/lib/commands.sh"
. "$script_dir/lib/generator.sh" . "$script_dir/lib/generator.sh"
. "$script_dir/lib/builder.sh" . "$script_dir/lib/builder.sh"
. "$script_dir/lib/runtime.sh"
src="" src=""
out="" out=""
@@ -27,6 +44,7 @@ post_title=""
positional_count=0 positional_count=0
watch_mode="false" watch_mode="false"
serve_mode="false" serve_mode="false"
serve_port=""
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
@@ -129,9 +147,17 @@ EOFCOMPS
*) *)
positional_count=$((positional_count + 1)) positional_count=$((positional_count + 1))
if [ "$positional_count" -eq 1 ]; then if [ "$positional_count" -eq 1 ]; then
if [ -z "$src" ]; then src="$1"; else die "Source already set (use either positional or --from)."; fi if [ -z "$src" ]; then
src="$1"
else
die "Source already set (use either positional or --from)."
fi
elif [ "$positional_count" -eq 2 ]; then elif [ "$positional_count" -eq 2 ]; then
if [ -z "$out" ]; then out="$1"; else die "Output already set (use either positional or --to)."; fi if [ -z "$out" ]; then
out="$1"
else
die "Output already set (use either positional or --to)."
fi
else else
die "Too many positional arguments." die "Too many positional arguments."
fi fi
@@ -158,138 +184,25 @@ if [ ! -d "$src" ]; then
if [ "$src" = "site" ]; then if [ "$src" = "site" ]; then
usage usage
exit 1 exit 1
else
die "Source directory '$src' does not exist."
fi fi
die "Source directory '$src' does not exist."
fi fi
IGNORE_ARGS="-name '.kewtignore' -o -path '$src/.*'" BASE_IGNORE_ARGS=$(build_rule_args "$src" ".kewtignore" "-name '.kewtignore' -o -path '$src/.*'")
BASE_HIDE_ARGS=$(build_rule_args "$src" ".kewthide" "-name '.kewtignore' -o -name '.kewthide' -o -name '.kewtpreserve' -o -path '$src/.*'")
PRESERVE_ARGS=$(build_rule_args "$src" ".kewtpreserve" "-false")
IGNORE_ARGS="$BASE_IGNORE_ARGS"
HIDE_ARGS="$BASE_HIDE_ARGS"
if [ -f "$src/.kewtignore" ]; then refresh_build_context
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
IGNORE_ARGS="$IGNORE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
IGNORE_ARGS="$IGNORE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewtignore"
fi
find "$src" -name .kewtignore > "$KEWT_TMPDIR/kewt_ignore"
while read -r ki; do
d="${ki%/.kewtignore}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
IGNORE_ARGS="$IGNORE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_ignore"
rm -f "$KEWT_TMPDIR/kewt_ignore"
HIDE_ARGS="-name '.kewtignore' -o -name '.kewthide' -o -name '.kewtpreserve' -o -path '$src/.*'"
if [ -f "$src/.kewthide" ]; then
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
HIDE_ARGS="$HIDE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewthide"
fi
find "$src" -name .kewthide > "$KEWT_TMPDIR/kewt_hide"
while read -r kh; do
d="${kh%/.kewthide}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
HIDE_ARGS="$HIDE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_hide"
rm -f "$KEWT_TMPDIR/kewt_hide"
PRESERVE_ARGS="-false"
if [ -f "$src/.kewtpreserve" ]; then
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(echo "$line" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$src/$pattern_clean' -o -path '$src/$pattern_clean/*'"
else
PRESERVE_ARGS="$PRESERVE_ARGS -o -name '$pattern_clean'"
fi
done < "$src/.kewtpreserve"
fi
find "$src" -name .kewtpreserve > "$KEWT_TMPDIR/kewt_preserve"
while read -r kp; do
d="${kp%/.kewtpreserve}"
if [ "$d" != "$src" ] && [ "$d" != "." ]; then
PRESERVE_ARGS="$PRESERVE_ARGS -o -path '$d' -o -path '$d/*'"
fi
done < "$KEWT_TMPDIR/kewt_preserve"
rm -f "$KEWT_TMPDIR/kewt_preserve"
load_config "./site.conf"
load_config "$src/site.conf"
if [ -n "$posts_dir" ]; then
HIDE_ARGS="$HIDE_ARGS -o -path '$src/$posts_dir/*'"
fi
[ "$post_mode" = "true" ] && create_new_post "$src" "$post_title" [ "$post_mode" = "true" ] && create_new_post "$src" "$post_title"
asset_version=""
if [ "$versioning" = "true" ]; then
asset_version="?v=$(date +%s)"
fi
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
if [ ! -f "$template" ]; then
template="$KEWT_TMPDIR/default_template.html"
printf '%s\n' "$DEFAULT_TMPL" > "$template"
fi
if [ "$clean_mode" = "true" ]; then if [ "$clean_mode" = "true" ]; then
[ -d "$out" ] && rm -rf "$out" [ -d "$out" ] && rm -rf "$out"
fi fi
mkdir -p "$out" mkdir -p "$out"
nav=$(generate_nav "$src")
extra_links=$(nav_links_html)
if [ -n "$extra_links" ]; then
nav="$nav
$extra_links"
fi
if [ -n "$nav_extra" ]; then
nav="$nav
$nav_extra"
fi
build_site build_site
if [ "$serve_mode" = "true" ]; then if [ "$serve_mode" = "true" ]; then
@@ -315,45 +228,17 @@ if [ "$watch_mode" = "true" ]; then
touch "$KEWT_TMPDIR/watch_mark" touch "$KEWT_TMPDIR/watch_mark"
while true; do while true; do
sleep 1 sleep 1
changed="$(find "$src" -type f -newer "$KEWT_TMPDIR/watch_mark" 2>/dev/null | head -n 1)" changed=$(watch_for_changes "$KEWT_TMPDIR/watch_mark")
[ -z "$changed" ] && [ -f "site.conf" ] && [ "site.conf" -nt "$KEWT_TMPDIR/watch_mark" ] && changed="site.conf"
[ -z "$changed" ] && [ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$KEWT_TMPDIR/watch_mark" ] && changed="$src/site.conf"
[ -z "$changed" ] && [ -f "$template" ] && [ "$template" -nt "$KEWT_TMPDIR/watch_mark" ] && changed="$template"
[ -z "$changed" ] && [ -d "$script_dir/styles" ] && changed="$(find "$script_dir/styles" -type f -newer "$KEWT_TMPDIR/watch_mark" 2>/dev/null | head -n 1)"
if [ -n "$changed" ]; then if [ -n "$changed" ]; then
echo "" echo ""
echo "Change detected, rebuilding..." echo "Change detected, rebuilding..."
if [ "$clean_mode" = "true" ]; then if [ "$clean_mode" = "true" ]; then
find "$out" -mindepth 1 -delete 2>/dev/null find "$out" -mindepth 1 -delete 2>/dev/null
fi fi
load_config "./site.conf" refresh_build_context
load_config "$src/site.conf"
asset_version=""
if [ "$versioning" = "true" ]; then
asset_version="?v=$(date +%s)"
fi
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
if [ ! -f "$template" ]; then
template="$KEWT_TMPDIR/default_template.html"
printf '%s\n' "$DEFAULT_TMPL" > "$template"
fi
nav=$(generate_nav "$src")
extra_links=$(nav_links_html)
if [ -n "$extra_links" ]; then
nav="$nav
$extra_links"
fi
if [ -n "$nav_extra" ]; then
nav="$nav
$nav_extra"
fi
build_site build_site
touch "$KEWT_TMPDIR/watch_mark" touch "$KEWT_TMPDIR/watch_mark"
fi fi

View File

@@ -1,3 +1,5 @@
#!/bin/sh
needs_rebuild() { needs_rebuild() {
src_file="$1" src_file="$1"
out_file="$2" out_file="$2"
@@ -12,9 +14,27 @@ needs_rebuild() {
return 1 return 1
} }
write_content_warning_outputs() {
_source_file="$1"
_content_out_file="$2"
_content_rel_url="$3"
_target_url="$4"
_landing_out_file="$5"
_is_home="$6"
is_cw_content_page="true"
render_markdown "$_source_file" "$_is_home" "$_target_url" > "$_content_out_file"
is_cw_content_page="false"
generate_content_warning_page "$fm_title" "$fm_content_warning" "$_content_rel_url" "$_target_url" "$_landing_out_file" "false"
}
build_site() { build_site() {
echo "Building site from '$src' to '$out'..." echo "Building site from '$src' to '$out'..."
build_markdown_manifest
build_full_nav
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while read -r dir; do eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while read -r dir; do
rel_dir="${dir#"$src"}" rel_dir="${dir#"$src"}"
rel_dir="${rel_dir#/}" rel_dir="${rel_dir#/}"
@@ -45,28 +65,24 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
if [ "$has_custom_index" = "false" ] || [ "$has_list" = "true" ]; then if [ "$has_custom_index" = "false" ] || [ "$has_list" = "true" ]; then
is_posts_dir="false" is_posts_dir="false"
if [ -n "$posts_dir" ] && { [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; }; then if is_posts_directory_rel "$rel_dir"; then
is_posts_dir="true" is_posts_dir="true"
fi fi
if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ] && [ "$has_list" = "false" ]; then if [ "$single_file_index" = "true" ] && [ "$is_posts_dir" = "false" ] && [ "$has_list" = "false" ]; then
md_count=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md" | wc -l) if load_manifest_dir_entry "$rel_dir" && [ "$dir_md_count" -eq 1 ]; then
if [ "$md_count" -eq 1 ]; then md_file="$src/$dir_first_md"
md_file=$(find "$dir" ! -name "$(basename "$dir")" -prune -name "*.md")
is_home="false"; [ "$dir" = "$src" ] && is_home="true" is_home="false"; [ "$dir" = "$src" ] && is_home="true"
target_url="/$rel_dir/index.html" target_url=$(directory_index_url "$rel_dir")
[ "$rel_dir" = "." ] && target_url="/index.html"
if needs_rebuild "$md_file" "$out_dir/index.html"; then if needs_rebuild "$md_file" "$out_dir/index.html"; then
parse_frontmatter "$md_file" parse_frontmatter "$md_file"
if [ -n "$fm_content_warning" ]; then if [ -n "$fm_content_warning" ]; then
content_out_file="$out_dir/content.html" content_out_file="$out_dir/content.html"
content_rel_url="/$rel_dir/content.html" if [ "$rel_dir" = "." ]; then
[ "$rel_dir" = "." ] && content_rel_url="/content.html" content_rel_url="/content.html"
else
is_cw_content_page="true" content_rel_url="/$(encode_url_path "$rel_dir")/content.html"
render_markdown "$md_file" "$is_home" "$target_url" > "$content_out_file" fi
is_cw_content_page="false" write_content_warning_outputs "$md_file" "$content_out_file" "$content_rel_url" "$target_url" "$out_dir/index.html" "$is_home"
generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false"
else else
render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html" render_markdown "$md_file" "$is_home" "$target_url" > "$out_dir/index.html"
fi fi
@@ -89,7 +105,7 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
sort_args="" sort_args=""
# If this is the posts dir reverse # If this is the posts dir reverse
if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then if is_posts_directory_rel "$rel_dir"; then
sort_args="-r" sort_args="-r"
fi fi
@@ -102,82 +118,50 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
template.html|site.conf|style.css|styles.root.css|index.md) continue ;; template.html|site.conf|style.css|styles.root.css|index.md) continue ;;
esac esac
if [ -d "$entry" ]; then if [ -d "$entry" ]; then
echo "${name}|- [${name}/](${name}/index.html)" >> "$temp_entries" entry_rel_dir="${entry#"$src"/}"
manifest_dir_hidden_by_draft_index "$entry_rel_dir" && continue
dir_url="$(encode_url_path "$name")/index.html"
echo "${name}|- [${name}/](${dir_url})" >> "$temp_entries"
elif [ "${entry%.md}" != "$entry" ]; then elif [ "${entry%.md}" != "$entry" ]; then
entry_rel_path="${entry#"$src"/}"
load_manifest_entry "$entry_rel_path" || continue
label="${name%.md}" label="${name%.md}"
[ "$manifest_draft" = "true" ] && continue
# Parse frontmatter for date/title/draft post_h="$manifest_title"
parse_frontmatter "$entry"
[ "$fm_draft" = "true" ] && continue
# Try to get first heading
post_h="$fm_title"
if [ -z "$post_h" ]; then
post_h=$(grep -m 1 '^# ' "$entry" | sed 's/^# *//')
if [ -n "$post_h" ]; then
post_h=$(echo "$post_h" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
fi
fi
is_post_entry="false" is_post_entry="false"
if [ "$rel_dir" = "$posts_dir" ] || [ "./$rel_dir" = "$posts_dir" ]; then if is_posts_directory_rel "$rel_dir"; then
is_post_entry="true" is_post_entry="true"
fi fi
if [ -n "$post_h" ]; then if [ -n "$post_h" ]; then
if [ "$is_post_entry" = "true" ]; then if [ "$is_post_entry" = "true" ]; then
# Use frontmatter date if available, else parse from filename if [ -n "$manifest_post_time" ]; then
if [ -n "$fm_date" ]; then label="$post_h - $manifest_post_date $manifest_post_time"
p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time=""
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
else else
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') label="$post_h - $manifest_post_date"
p_time="00:00"
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
fi
if [ -n "$p_time" ]; then
label="$post_h - $p_date $p_time"
else
label="$post_h - $p_date"
fi fi
else else
label="$post_h" label="$post_h"
fi fi
elif [ "$is_post_entry" = "true" ]; then elif [ "$is_post_entry" = "true" ]; then
# No heading; use date if [ -n "$manifest_post_time" ]; then
if [ -n "$fm_date" ]; then label="$manifest_post_date $manifest_post_time"
p_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
p_time=""
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
if [ -n "$p_time" ]; then
label="$p_date $p_time"
else
label="$p_date"
fi
else else
p_date=$(echo "${name%.md}" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') label="$manifest_post_date"
p_time="00:00"
if echo "${name%.md}" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
p_time=$(echo "${name%.md}" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
label="$p_date $p_time"
fi fi
fi fi
if [ "$is_post_entry" = "true" ]; then if [ "$is_post_entry" = "true" ]; then
sort_key="${p_date} ${p_time}" sort_key="${manifest_post_date} ${manifest_post_time}"
else else
sort_key="$name" sort_key="$name"
fi fi
echo "${sort_key}|- [$label](${name%.md}.html)|$name|${name%.md}.html" >> "$temp_entries" entry_url=$(encode_url_path "${name%.md}.html")
echo "${sort_key}|- [$label](${entry_url})|$name|${entry_url}" >> "$temp_entries"
else else
echo "${name}|- [$name]($name)|$name|$name" >> "$temp_entries" asset_url=$(encode_url_path "$name")
echo "${name}|- [$name]($asset_url)|$name|$asset_url" >> "$temp_entries"
fi fi
done done
@@ -214,13 +198,13 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
rm -f "$temp_entries" rm -f "$temp_entries"
is_home="false"; [ "$dir" = "$src" ] && is_home="true" is_home="false"; [ "$dir" = "$src" ] && is_home="true"
target_url="/$rel_dir/index.html" target_url=$(directory_index_url "$rel_dir")
[ "$rel_dir" = "." ] && target_url="/index.html"
num_items=$(wc -l < "$temp_list") num_items=$(wc -l < "$temp_list")
if [ "$is_posts_dir" = "true" ] && [ -n "$posts_per_page" ] && [ "$posts_per_page" -gt 0 ] && [ "$num_items" -gt "$posts_per_page" ]; then if [ "$is_posts_dir" = "true" ] && [ -n "$posts_per_page" ] && [ "$posts_per_page" -gt 0 ] && [ "$num_items" -gt "$posts_per_page" ]; then
num_pages=$(( (num_items + posts_per_page - 1) / posts_per_page )) num_pages=$(( (num_items + posts_per_page - 1) / posts_per_page ))
for p in $(seq 1 $num_pages); do p=1
while [ "$p" -le "$num_pages" ]; do
chunk_list="$KEWT_TMPDIR/chunk.md" chunk_list="$KEWT_TMPDIR/chunk.md"
start_line=$(( (p - 1) * posts_per_page + 1 )) start_line=$(( (p - 1) * posts_per_page + 1 ))
tail -n +$start_line "$temp_list" | head -n "$posts_per_page" > "$chunk_list" tail -n +$start_line "$temp_list" | head -n "$posts_per_page" > "$chunk_list"
@@ -279,6 +263,7 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
render_markdown "$temp_index_p" "$is_home" "$target_url_p" > "$out_file" render_markdown "$temp_index_p" "$is_home" "$target_url_p" > "$out_file"
rm -f "$temp_index_p" "$chunk_list" rm -f "$temp_index_p" "$chunk_list"
p=$((p + 1))
done done
else else
if [ "$has_custom_index" = "true" ]; then if [ "$has_custom_index" = "true" ]; then
@@ -317,14 +302,12 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type d -print" | sort | while
if [ -n "$fm_content_warning" ]; then if [ -n "$fm_content_warning" ]; then
content_out_file="$out_dir/content.html" content_out_file="$out_dir/content.html"
content_rel_url="/$rel_dir/content.html" if [ "$rel_dir" = "." ]; then
[ "$rel_dir" = "." ] && content_rel_url="/content.html" content_rel_url="/content.html"
else
is_cw_content_page="true" content_rel_url="/$(encode_url_path "$rel_dir")/content.html"
render_markdown "$temp_index" "$is_home" "$target_url" > "$content_out_file" fi
is_cw_content_page="false" write_content_warning_outputs "$temp_index" "$content_out_file" "$content_rel_url" "$target_url" "$out_dir/index.html" "$is_home"
generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$target_url" "$out_dir/index.html" "false"
else else
render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html" render_markdown "$temp_index" "$is_home" "$target_url" > "$out_dir/index.html"
fi fi
@@ -373,34 +356,27 @@ eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -type f -print" | sort | while
fi fi
is_posts_dir_2="false" is_posts_dir_2="false"
if [ -n "$posts_dir" ] && { [ "$dir_rel" = "$posts_dir" ] || [ "./$dir_rel" = "$posts_dir" ]; }; then if is_posts_directory_rel "$dir_rel"; then
is_posts_dir_2="true" is_posts_dir_2="true"
fi fi
if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; then if [ "$single_file_index" = "true" ] && [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ] && [ ! -f "$(dirname "$file")/index.md" ] && [ "$is_posts_dir_2" = "false" ]; then
md_count=$(find "$(dirname "$file")" ! -name "$(basename "$(dirname "$file")")" -prune -name "*.md" | wc -l) load_manifest_dir_entry "$dir_rel" && [ "$dir_md_count" -eq 1 ] && continue
[ "$md_count" -eq 1 ] && continue
fi fi
if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then if [ "${file%.md}" != "$file" ] && [ "$is_preserved" -eq 0 ]; then
# Skip draft files load_manifest_entry "$rel_path" || continue
parse_frontmatter "$file" [ "$manifest_draft" = "true" ] && continue
if [ "$fm_draft" = "true" ]; then
continue
fi
is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true" is_home="false"; [ "$file" = "$src/index.md" ] && is_home="true"
out_file="$out/${rel_path%.md}.html" out_file="$out/${rel_path%.md}.html"
if needs_rebuild "$file" "$out_file"; then if needs_rebuild "$file" "$out_file"; then
if [ -n "$fm_content_warning" ]; then fm_title="$manifest_title"
fm_content_warning="$manifest_content_warning"
if [ -n "$manifest_content_warning" ]; then
content_out_file="$out/${rel_path%.md}-content.html" content_out_file="$out/${rel_path%.md}-content.html"
content_rel_url="/${rel_path%.md}-content.html" content_rel_url="/$(encode_url_path "${rel_path%.md}")-content.html"
orig_rel_url="/${rel_path%.md}.html" orig_rel_url="$manifest_url"
write_content_warning_outputs "$file" "$content_out_file" "$content_rel_url" "$orig_rel_url" "$out_file" "$is_home"
is_cw_content_page="true"
render_markdown "$file" "$is_home" "$orig_rel_url" > "$content_out_file"
is_cw_content_page="false"
generate_content_warning_page "$fm_title" "$fm_content_warning" "$content_rel_url" "$orig_rel_url" "$out_file" "false"
else else
render_markdown "$file" "$is_home" > "$out_file" render_markdown "$file" "$is_home" > "$out_file"
fi fi
@@ -464,85 +440,32 @@ if [ "$generate_feed" = "true" ] && [ -n "$base_url" ]; then
temp_feed_files="$KEWT_TMPDIR/feed_files_$$.txt" temp_feed_files="$KEWT_TMPDIR/feed_files_$$.txt"
: > "$temp_feed_files" : > "$temp_feed_files"
find "$src" -type f -name '*.md' -path "*${posts_dir:-__no_posts__}*" -print | while IFS= read -r post_file; do while IFS= read -r manifest_rel_path; do
post_basename=$(basename "$post_file" .md) case "$manifest_rel_path" in
# Parse frontmatter to get date *"${posts_dir:-__no_posts__}"*) ;;
parse_frontmatter "$post_file" *) continue ;;
[ "$fm_draft" = "true" ] && continue esac
if [ -n "$fm_date" ]; then load_manifest_entry "$manifest_rel_path" || continue
post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') [ "$manifest_draft" = "true" ] && continue
post_time="00:00" printf '%s %s|%s\n' "$manifest_post_date" "$manifest_post_time" "$manifest_rel_path" >> "$temp_feed_files"
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then done < "$manifest_all_list"
post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi LC_ALL=C sort -r "$temp_feed_files" | cut -d'|' -f2- | while IFS= read -r post_rel_path; do
else load_manifest_entry "$post_rel_path" || continue
post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/') [ "$manifest_draft" = "true" ] && continue
post_time="00:00"
if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then post_date="$manifest_post_date"
post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':') post_time="$manifest_post_time"
fi post_heading="$manifest_title"
post_slug="$manifest_post_slug"
if [ -z "$post_heading" ] && [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then
post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')
fi fi
echo "${post_date} ${post_time}|${post_file}" >> "$temp_feed_files"
done
LC_ALL=C sort -r "$temp_feed_files" | cut -d'|' -f2- | while IFS= read -r post_file; do
post_basename=$(basename "$post_file" .md)
# Parse frontmatter
parse_frontmatter "$post_file"
[ "$fm_draft" = "true" ] && continue
# Use frontmatter date, fallback to filename
if [ -n "$fm_date" ]; then
post_date=$(echo "$fm_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time="00:00"
if echo "$fm_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$fm_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
else
post_date=$(echo "$post_basename" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time="00:00"
if echo "$post_basename" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$post_basename" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
fi
post_slug=$(echo "$post_basename" | sed -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' -e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' -e 's/^[_\-]//')
post_heading="$fm_title"
if [ -z "$post_heading" ]; then
post_heading=$(grep -m 1 '^# ' "$post_file" | sed 's/^# *//')
fi
if [ -z "$post_heading" ]; then
if [ -n "$post_slug" ] && ! echo "$post_slug" | grep -q '^[0-9]\+$'; then
post_heading=$(echo "$post_slug" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)}1')
else
post_heading="Post"
fi
fi
post_heading=$(echo "$post_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
feed_post_title="$post_heading - $post_date $post_time" feed_post_title="$post_heading - $post_date $post_time"
rel_path="${post_file#"$src"}" post_url="$base_url_feed$manifest_url"
rel_path="${rel_path#/}"
post_url="$base_url_feed/${rel_path%.md}.html"
if date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000' >/dev/null 2>&1; then pub_date=$(format_rfc2822_utc "$post_date" "$post_time")
pub_date=$(date -u -d "$post_date $post_time" '+%a, %d %b %Y %H:%M:%S +0000')
else
pub_year=$(echo "$post_date" | cut -d- -f1)
pub_month=$(echo "$post_date" | cut -d- -f2)
pub_day=$(echo "$post_date" | cut -d- -f3)
# zero-padded
pub_day=$(printf '%02d' "${pub_day#0}")
case "$pub_month" in
01) pub_mon="Jan" ;; 02) pub_mon="Feb" ;; 03) pub_mon="Mar" ;;
04) pub_mon="Apr" ;; 05) pub_mon="May" ;; 06) pub_mon="Jun" ;;
07) pub_mon="Jul" ;; 08) pub_mon="Aug" ;; 09) pub_mon="Sep" ;;
10) pub_mon="Oct" ;; 11) pub_mon="Nov" ;; 12) pub_mon="Dec" ;;
esac
pub_date="Mon, ${pub_day} ${pub_mon} ${pub_year} ${post_time}:00 +0000"
fi
{ {
printf ' <item>\n' printf ' <item>\n'
@@ -566,25 +489,25 @@ if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then
temp_tags="$KEWT_TMPDIR/tags_$$.txt" temp_tags="$KEWT_TMPDIR/tags_$$.txt"
: > "$temp_tags" : > "$temp_tags"
eval "find \"$src\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r md_file; do while IFS= read -r rel_path; do
is_index="false" load_manifest_entry "$rel_path" || continue
[ "$(basename "$md_file")" = "index.md" ] && is_index="true"
rel_path="${md_file#"$src"}" if [ "$manifest_is_index" = "true" ]; then
rel_path="${rel_path#/}"
if [ "$is_index" = "true" ]; then
if [ "$rel_path" = "index.md" ]; then if [ "$rel_path" = "index.md" ]; then
md_url="/index.html" md_url="/index.html"
else else
md_url="/${rel_path%/index.md}/index.html" md_url=$(directory_index_url "${rel_path%/index.md}")
fi fi
else else
md_url="/${rel_path%.md}.html" md_url="$manifest_url"
if [ "$single_file_index" = "true" ]; then if [ "$single_file_index" = "true" ]; then
dir_of_file="$(dirname "$md_file")" rel_dir_of_file="$manifest_dir_rel"
rel_dir_of_file="${dir_of_file#"$src"}"
rel_dir_of_file="${rel_dir_of_file#/}"
[ -z "$rel_dir_of_file" ] && rel_dir_of_file="." [ -z "$rel_dir_of_file" ] && rel_dir_of_file="."
if [ "$rel_dir_of_file" = "." ]; then
dir_of_file="$src"
else
dir_of_file="$src/$rel_dir_of_file"
fi
is_posts_dir_search="false" is_posts_dir_search="false"
if [ -n "$posts_dir" ] && { [ "$rel_dir_of_file" = "$posts_dir" ] || [ "./$rel_dir_of_file" = "$posts_dir" ]; }; then if [ -n "$posts_dir" ] && { [ "$rel_dir_of_file" = "$posts_dir" ] || [ "./$rel_dir_of_file" = "$posts_dir" ]; }; then
@@ -592,63 +515,23 @@ if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then
fi fi
if [ "$is_posts_dir_search" = "false" ] && [ ! -f "$dir_of_file/index.md" ]; then if [ "$is_posts_dir_search" = "false" ] && [ ! -f "$dir_of_file/index.md" ]; then
md_count_search=$(find "$dir_of_file" ! -name "$(basename "$dir_of_file")" -prune -name "*.md" | wc -l) if load_manifest_dir_entry "$rel_dir_of_file" && [ "$dir_md_count" -eq 1 ]; then
if [ "$md_count_search" -eq 1 ]; then
if [ "$rel_dir_of_file" = "." ]; then if [ "$rel_dir_of_file" = "." ]; then
md_url="/index.html" md_url="/index.html"
else else
md_url="/$rel_dir_of_file/index.html" md_url=$(directory_index_url "$rel_dir_of_file")
fi fi
fi fi
fi fi
fi fi
fi fi
parse_frontmatter "$md_file" [ "$manifest_draft" = "true" ] && continue
[ "$fm_draft" = "true" ] && continue md_heading="$manifest_title"
md_heading="$fm_title"
if [ -z "$md_heading" ]; then
md_heading=$(grep -m 1 '^# ' "$md_file" | sed 's/^# *//; s/ *$//')
if [ -n "$md_heading" ]; then
md_heading=$(echo "$md_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g')
fi
fi
if [ -z "$md_heading" ]; then
basename_no_ext=$(basename "$md_file" .md)
if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then
md_heading=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
else
md_heading="$title - Page"
fi
fi
if [ "$generate_search" = "true" ]; then if [ "$generate_search" = "true" ]; then
if [ -z "$fm_content_warning" ] || [ "$include_cw_pages_in_search" = "true" ]; then if [ -z "$manifest_content_warning" ] || [ "$include_cw_pages_in_search" = "true" ]; then
md_content=$(awk '{ md_content="$manifest_search_content"
if (NR == 1 && $0 == "---") { in_fm = 1; next }
if (in_fm && $0 == "---") { in_fm = 0; next }
if (in_fm) next
if ($0 ~ /^```/) { in_code = !in_code; next }
if (in_code) next
print
}' "$md_file" | sed \
-e 's/^#\{1,6\} //' \
-e 's/\*\*\([^*]*\)\*\*/\1/g' \
-e 's/\*\([^*]*\)\*/\1/g' \
-e 's/__\([^_]*\)__/\1/g' \
-e 's/_\([^_]*\)_/\1/g' \
-e 's/`\([^`]*\)`/\1/g' \
-e 's/\[\([^]]*\)](\([^)]*\))/\1/g' \
-e 's/!\[\([^]]*\)](\([^)]*\))//g' \
-e 's/^[[:space:]]*[-*+] //' \
-e 's/^[[:space:]]*[0-9]\{1,\}\. //' \
-e 's/^>[[:space:]]*//' \
-e 's/<[^>]*>//g' \
-e '/^[[:space:]]*$/d' \
-e 's/|//g' \
-e 's/^[[:space:]]*---[[:space:]]*$//' \
| tr '\n' ' ' | sed -e 's/ */ /g' -e 's/\\/\\\\/g' -e 's/"/\\"/g' | head -c 500)
if [ "$first_search_item" = "false" ]; then if [ "$first_search_item" = "false" ]; then
printf ',\n' >> "$out/search.json" printf ',\n' >> "$out/search.json"
fi fi
@@ -657,17 +540,17 @@ if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then
fi fi
fi fi
if [ "$generate_tags" = "true" ] && [ -n "$fm_tags" ]; then if [ "$generate_tags" = "true" ] && [ -n "$manifest_tags" ]; then
old_ifs=$IFS old_ifs=$IFS
IFS=',' IFS=','
for tag in $fm_tags; do for tag in $manifest_tags; do
tag=$(echo "$tag" | sed 's/^[ \t]*//;s/[ \t]*$//') tag=$(echo "$tag" | sed 's/^[ \t]*//;s/[ \t]*$//')
[ -z "$tag" ] && continue [ -z "$tag" ] && continue
printf '%s|%s|%s\n' "$tag" "$md_url" "$md_heading" >> "$temp_tags" printf '%s|%s|%s\n' "$tag" "$md_url" "$md_heading" >> "$temp_tags"
done done
IFS=$old_ifs IFS=$old_ifs
fi fi
done done < "$manifest_visible_list"
if [ "$generate_search" = "true" ]; then if [ "$generate_search" = "true" ]; then
printf '\n]\n' >> "$out/search.json" printf '\n]\n' >> "$out/search.json"
@@ -699,7 +582,7 @@ if [ "$generate_search" = "true" ] || [ "$generate_tags" = "true" ]; then
cut -d'|' -f1 "$temp_tags" | sort -u | while IFS= read -r tag; do cut -d'|' -f1 "$temp_tags" | sort -u | while IFS= read -r tag; do
tag_slug=$(echo "$tag" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g') tag_slug=$(echo "$tag" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g')
echo "- [$tag](/$(echo $tags_dir | sed 's|^\/||; s|\/$||')/$tag_slug.html)" >> "$tags_index_md" echo "- [$tag](/$(echo "$tags_dir" | sed 's|^\/||; s|\/$||')/$tag_slug.html)" >> "$tags_index_md"
tag_page_md="$KEWT_TMPDIR/tag_page_$$.md" tag_page_md="$KEWT_TMPDIR/tag_page_$$.md"
echo "# Tag: $tag" > "$tag_page_md" echo "# Tag: $tag" > "$tag_page_md"

View File

@@ -1,3 +1,5 @@
#!/bin/sh
usage() { usage() {
invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}") invoked_as=$(basename "${KEWT_INVOKED_AS:-$0}")
cat <<EOF cat <<EOF

View File

@@ -1,3 +1,5 @@
#!/bin/sh
DEFAULT_CONF='title = "kewt" DEFAULT_CONF='title = "kewt"
style = "kewt" style = "kewt"
lang = "en" lang = "en"
@@ -49,6 +51,7 @@ DEFAULT_TMPL='<!doctype html>
<input type="checkbox" id="nav-toggle" class="nav-toggle" aria-hidden="true" /> <input type="checkbox" id="nav-toggle" class="nav-toggle" aria-hidden="true" />
<header> <header>
<h1>{{HEADER_BRAND}}</h1> <h1>{{HEADER_BRAND}}</h1>
{{HEADER_SEARCH}}
<label for="nav-toggle" class="nav-toggle-label" aria-hidden="true">&#9776;</label> <label for="nav-toggle" class="nav-toggle-label" aria-hidden="true">&#9776;</label>
</header> </header>
@@ -59,42 +62,43 @@ DEFAULT_TMPL='<!doctype html>
</body> </body>
</html>' </html>'
title="kewt" reset_config() {
style="kewt" title="kewt"
lang="en" style="kewt"
draft_by_default="false" lang="en"
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>" draft_by_default="false"
dir_indexes="true" dir_indexes="true"
single_file_index="true" single_file_index="true"
flatten="false" flatten="false"
order="" order=""
home_name="Home" home_name="Home"
show_home_in_nav="true" show_home_in_nav="true"
nav_links="" nav_links=""
nav_extra="" nav_extra=""
footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>" footer="made with <a href=\"https://kewt.krzak.org\">kewt</a>"
logo="" logo=""
display_logo="false" display_logo="false"
display_title="true" display_title="true"
logo_as_favicon="true" logo_as_favicon="true"
favicon="" favicon=""
generate_page_title="true" generate_page_title="true"
error_page="not_found.html" error_page="not_found.html"
versioning="false" versioning="false"
enable_header_links="true" enable_header_links="true"
base_url="" base_url=""
generate_feed="false" generate_feed="false"
feed_file="rss.xml" feed_file="rss.xml"
posts_dir="" posts_dir=""
posts_per_page="12" posts_per_page="12"
custom_admonitions="" custom_admonitions=""
cw_hide_url="true" cw_hide_url="true"
generate_tags="false" generate_tags="false"
tags_dir="tags" tags_dir="tags"
generate_search="false" generate_search="false"
search_in_footer="false" search_in_footer="false"
search_in_header="false" search_in_header="false"
include_cw_pages_in_search="false" include_cw_pages_in_search="false"
}
load_config() { load_config() {
[ -f "$1" ] || return [ -f "$1" ] || return
@@ -160,3 +164,5 @@ load_config() {
esac esac
done < "$1" done < "$1"
} }
reset_config

View File

@@ -1,14 +1,41 @@
#!/bin/sh
SEARCH_FORM_FOOTER='<form class="kewt-search-footer" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>' SEARCH_FORM_FOOTER='<form class="kewt-search-footer" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>'
SEARCH_FORM_HEADER='<form class="kewt-search-header" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>' SEARCH_FORM_HEADER='<form class="kewt-search-header" action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form>'
SEARCH_FORM_NAV='<div class="kewt-search-nav"><form action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form></div>' SEARCH_FORM_NAV='<div class="kewt-search-nav"><form action="/search.html" method="get"><input type="text" name="q" placeholder="Search..." required><button type="submit">Go</button></form></div>'
generate_nav() { generate_nav() {
dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk") dinfo=$(eval "find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -print" | sort | AWK_SRC="$1" awk -f "$awk_dir/collect_dir_info.awk")
find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" nav_input="$KEWT_TMPDIR/nav_input.lst"
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then : > "$nav_input"
find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\""
if [ -f "$manifest_visible_list" ]; then
while IFS= read -r nav_rel_path; do
printf '%s/%s\n' "$1" "$nav_rel_path" >> "$nav_input"
done < "$manifest_visible_list"
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ] && ! manifest_dir_hidden_by_draft_index "$posts_dir"; then
has_posts_nav_entry="false"
has_posts_index_entry="false"
while IFS= read -r nav_rel_path; do
case "$nav_rel_path" in
"$posts_dir"/index.md) has_posts_index_entry="true" ;;
"$posts_dir"/*) has_posts_nav_entry="true" ;;
esac
done < "$manifest_visible_list"
if [ "$has_posts_nav_entry" = "true" ] && [ "$has_posts_index_entry" = "false" ]; then
printf '%s/%s/index.md\n' "$1" "$posts_dir" >> "$nav_input"
fi
fi
else
find_cmd="find \"$1\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print"
if [ -n "$posts_dir" ] && [ -d "$1/$posts_dir" ]; then
find_cmd="$find_cmd && echo \"$1/$posts_dir/index.md\""
fi
eval "$find_cmd" | sort -u > "$nav_input"
fi fi
eval "$find_cmd" | sort -u | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk"
sort -u "$nav_input" | AWK_SRC="$1" AWK_SINGLE_FILE_INDEX="$single_file_index" AWK_FLATTEN="$flatten" AWK_ORDER="$order" AWK_HOME_NAME="$home_name" AWK_SHOW_HOME_IN_NAV="$show_home_in_nav" AWK_DINFO="$dinfo" awk -f "$awk_dir/generate_sidebar.awk"
} }
escape_html_text() { escape_html_text() {
printf '%s' "$1" | sed \ printf '%s' "$1" | sed \
@@ -23,29 +50,6 @@ escape_html_attr() {
-e 's/</\&lt;/g' \ -e 's/</\&lt;/g' \
-e 's/>/\&gt;/g' -e 's/>/\&gt;/g'
} }
parse_frontmatter() {
_fm_file="$1"
_fm_out="$KEWT_TMPDIR/fm_vals.txt"
: > "$_fm_out"
awk -v fm_out="$_fm_out" -f "$awk_dir/frontmatter.awk" "$_fm_file" > /dev/null
fm_title=""
fm_date=""
fm_draft=""
fm_description=""
fm_content_warning=""
fm_tags=""
while IFS='=' read -r _fk _fv; do
case "$_fk" in
title) fm_title="$_fv" ;;
date) fm_date="$_fv" ;;
draft) fm_draft="$_fv" ;;
description) fm_description="$_fv" ;;
content_warning) fm_content_warning="$_fv" ;;
tags) fm_tags="$_fv" ;;
esac
done < "$_fm_out"
rm -f "$_fm_out"
}
nav_links_html() { nav_links_html() {
[ -n "$nav_links" ] || return [ -n "$nav_links" ] || return
@@ -140,7 +144,7 @@ render_markdown() {
else else
rel_path="${file#"$src"}" rel_path="${file#"$src"}"
rel_path="${rel_path#/}" rel_path="${rel_path#/}"
current_url="/${rel_path%.md}.html" current_url=$(markdown_file_url "$rel_path")
fi fi
content_file="$file" content_file="$file"
@@ -181,7 +185,7 @@ render_markdown() {
"$src/styles.css") style_rel_to_src="styles.css" ;; "$src/styles.css") style_rel_to_src="styles.css" ;;
"$src/style.css") style_rel_to_src="style.css" ;; "$src/style.css") style_rel_to_src="style.css" ;;
esac esac
style_path="/${style_rel_to_src%styles.css}" style_path="/$(encode_url_path "${style_rel_to_src%styles.css}")"
style_path="${style_path%style.css}styles.css" style_path="${style_path%style.css}styles.css"
else else
style_path="/styles.css" style_path="/styles.css"
@@ -233,9 +237,9 @@ render_markdown() {
if [ "$is_home" = "true" ] && [ -n "$home_name" ]; then if [ "$is_home" = "true" ] && [ -n "$home_name" ]; then
page_title="$home_name - $title" page_title="$home_name - $title"
else else
first_heading=$(grep -m 1 '^# ' "$file" | sed 's/^# *//; s/ *$//') first_heading=$(first_heading_from_markdown "$file")
if [ -n "$first_heading" ]; then if [ -n "$first_heading" ]; then
first_heading=$(echo "$first_heading" | sed -e 's/\[//g' -e 's/\]//g' -e 's/!//g' -e 's/\*//g' -e 's/_//g' -e 's/`//g' -e 's/([^)]*)//g' | sed 's/\\//g') first_heading=$(strip_markdown_text "$first_heading")
page_title="$first_heading - $title" page_title="$first_heading - $title"
else else
basename_no_ext=$(basename "$file" .md) basename_no_ext=$(basename "$file" .md)
@@ -275,13 +279,14 @@ render_markdown() {
final_nav="$nav" final_nav="$nav"
final_header_brand="$header_brand" final_header_brand="$header_brand"
final_header_search=""
if [ "$search_in_header" = "true" ]; then if [ "$search_in_header" = "true" ]; then
final_header_brand="$header_brand $SEARCH_FORM_HEADER" final_header_search="$SEARCH_FORM_HEADER"
final_nav="$SEARCH_FORM_NAV final_nav="$SEARCH_FORM_NAV
$nav" $nav"
fi fi
ENABLE_HEADER_LINKS="$enable_header_links" CUSTOM_ADMONITIONS="$custom_admonitions" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_LANG="$lang" AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$final_nav" AWK_FOOTER="$final_footer" AWK_STYLE_PATH="${style_path}" AWK_HEADER_BRAND="$final_header_brand" AWK_HEAD_EXTRA="$head_extra" AWK_VERSION="$asset_version" AWK_CONTENT_WARNING="$fm_content_warning" awk -f "$awk_dir/render_template.awk" "$local_template" ENABLE_HEADER_LINKS="$enable_header_links" CUSTOM_ADMONITIONS="$custom_admonitions" MARKDOWN_SITE_ROOT="$src" MARKDOWN_FALLBACK_FILE="$script_dir/styles/$style.css" sh "$script_dir/markdown.sh" "$content_file" | AWK_LANG="$lang" AWK_CURRENT_URL="$current_url" AWK_TITLE="$page_title" AWK_NAV="$final_nav" AWK_FOOTER="$final_footer" AWK_STYLE_PATH="${style_path}" AWK_HEADER_BRAND="$final_header_brand" AWK_HEADER_SEARCH="$final_header_search" AWK_HEAD_EXTRA="$head_extra" AWK_VERSION="$asset_version" AWK_CONTENT_WARNING="$fm_content_warning" awk -f "$awk_dir/render_template.awk" "$local_template"
} }
generate_content_warning_page() { generate_content_warning_page() {
_fm_title="$1" _fm_title="$1"

193
lib/manifest.sh Normal file
View File

@@ -0,0 +1,193 @@
#!/bin/sh
shell_quote() {
printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
}
manifest_meta_path() {
printf '%s/manifest/meta/%s.meta\n' "$KEWT_TMPDIR" "$1"
}
manifest_dir_meta_path() {
printf '%s/manifest/dir-meta/%s.meta\n' "$KEWT_TMPDIR" "$1"
}
manifest_dir_hidden_by_draft_index() {
_manifest_hidden_dir="${1:-.}"
[ -f "$manifest_hidden_dirs_list" ] || return 1
while :; do
awk -v dir="$_manifest_hidden_dir" '$0 == dir { found = 1 } END { exit(found ? 0 : 1) }' "$manifest_hidden_dirs_list" >/dev/null 2>&1 && return 0
[ "$_manifest_hidden_dir" = "." ] && return 1
_manifest_hidden_parent=$(dirname "$_manifest_hidden_dir")
[ "$_manifest_hidden_parent" = "$_manifest_hidden_dir" ] && return 1
_manifest_hidden_dir="$_manifest_hidden_parent"
done
}
write_manifest_dir_meta() {
_dir_meta_rel="$1"
_dir_meta_count="$2"
_dir_meta_first="$3"
_dir_meta_has_index="$4"
_dir_meta_path=$(manifest_dir_meta_path "$_dir_meta_rel")
mkdir -p "$(dirname "$_dir_meta_path")"
{
printf 'dir_manifest_rel=%s\n' "$(shell_quote "$_dir_meta_rel")"
printf 'dir_md_count=%s\n' "$(shell_quote "$_dir_meta_count")"
printf 'dir_first_md=%s\n' "$(shell_quote "$_dir_meta_first")"
printf 'dir_has_index=%s\n' "$(shell_quote "$_dir_meta_has_index")"
} > "$_dir_meta_path"
}
load_manifest_dir_entry() {
_dir_manifest_rel="$1"
_dir_manifest_meta_path=$(manifest_dir_meta_path "$_dir_manifest_rel")
[ -f "$_dir_manifest_meta_path" ] || return 1
# shellcheck disable=SC1090
. "$_dir_manifest_meta_path"
}
load_manifest_entry() {
_manifest_rel_path="$1"
_manifest_meta_path=$(manifest_meta_path "$_manifest_rel_path")
[ -f "$_manifest_meta_path" ] || return 1
# shellcheck disable=SC1090
. "$_manifest_meta_path"
}
extract_search_content() {
_search_file="$1"
awk '{
if (NR == 1 && $0 == "---") { in_fm = 1; next }
if (in_fm && $0 == "---") { in_fm = 0; next }
if (in_fm) next
if ($0 ~ /^```/) { in_code = !in_code; next }
if (in_code) next
print
}' "$_search_file" | sed \
-e 's/^#\{1,6\} //' \
-e 's/\*\*\([^*]*\)\*\*/\1/g' \
-e 's/\*\([^*]*\)\*/\1/g' \
-e 's/__\([^_]*\)__/\1/g' \
-e 's/_\([^_]*\)_/\1/g' \
-e 's/`\([^`]*\)`/\1/g' \
-e 's/\[\([^]]*\)](\([^)]*\))/\1/g' \
-e 's/!\[\([^]]*\)](\([^)]*\))//g' \
-e 's/^[[:space:]]*[-*+] //' \
-e 's/^[[:space:]]*[0-9]\{1,\}\. //' \
-e 's/^>[[:space:]]*//' \
-e 's/<[^>]*>//g' \
-e '/^[[:space:]]*$/d' \
-e 's/|//g' \
-e 's/^[[:space:]]*---[[:space:]]*$//' |
tr '\n' ' ' |
sed -e 's/ */ /g' -e 's/\\/\\\\/g' -e 's/"/\\"/g' |
awk '{ print substr($0, 1, 500) }'
}
build_markdown_manifest() {
manifest_root="$KEWT_TMPDIR/manifest"
manifest_meta_root="$manifest_root/meta"
manifest_dir_meta_root="$manifest_root/dir-meta"
manifest_all_list="$manifest_root/all.lst"
manifest_visible_list="$manifest_root/visible.lst"
manifest_hidden_dirs_list="$manifest_root/hidden-dirs.lst"
rm -rf "$manifest_root"
mkdir -p "$manifest_meta_root"
mkdir -p "$manifest_dir_meta_root"
: > "$manifest_all_list"
: > "$manifest_visible_list"
: > "$manifest_hidden_dirs_list"
eval "find \"$src\" \( $IGNORE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r manifest_file; do
manifest_rel_path="${manifest_file#"$src"/}"
manifest_dir_rel=$(dirname "$manifest_rel_path")
manifest_filename=$(basename "$manifest_rel_path")
manifest_is_index="false"
[ "$manifest_filename" = "index.md" ] && manifest_is_index="true"
parse_frontmatter "$manifest_file"
if [ "$manifest_filename" = "index.md" ] && [ "$fm_draft" = "true" ]; then
printf '%s\n' "$manifest_dir_rel" >> "$manifest_hidden_dirs_list"
fi
markdown_title_from_loaded_file "$manifest_file" "$title - Page"
manifest_title="$markdown_title"
set_post_datetime "$fm_date" "$(basename "$manifest_file" .md)"
manifest_post_date="$post_date"
manifest_post_time="$post_time"
manifest_post_slug=$(basename "$manifest_file" .md | sed \
-e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' \
-e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' \
-e 's/^[_\-]//')
if [ "$manifest_is_index" = "true" ]; then
if [ "$manifest_rel_path" = "index.md" ]; then
manifest_url="/index.html"
else
manifest_url=$(directory_index_url "${manifest_rel_path%/index.md}")
fi
else
manifest_url=$(markdown_file_url "$manifest_rel_path")
fi
manifest_search_content=""
if [ "$generate_search" = "true" ]; then
manifest_search_content=$(extract_search_content "$manifest_file")
fi
manifest_meta_file=$(manifest_meta_path "$manifest_rel_path")
mkdir -p "$(dirname "$manifest_meta_file")"
{
printf 'manifest_rel_path=%s\n' "$(shell_quote "$manifest_rel_path")"
printf 'manifest_dir_rel=%s\n' "$(shell_quote "$manifest_dir_rel")"
printf 'manifest_filename=%s\n' "$(shell_quote "$manifest_filename")"
printf 'manifest_is_index=%s\n' "$(shell_quote "$manifest_is_index")"
printf 'manifest_title=%s\n' "$(shell_quote "$manifest_title")"
printf 'manifest_date=%s\n' "$(shell_quote "$fm_date")"
printf 'manifest_draft=%s\n' "$(shell_quote "$fm_draft")"
printf 'manifest_description=%s\n' "$(shell_quote "$fm_description")"
printf 'manifest_content_warning=%s\n' "$(shell_quote "$fm_content_warning")"
printf 'manifest_tags=%s\n' "$(shell_quote "$fm_tags")"
printf 'manifest_url=%s\n' "$(shell_quote "$manifest_url")"
printf 'manifest_search_content=%s\n' "$(shell_quote "$manifest_search_content")"
printf 'manifest_post_date=%s\n' "$(shell_quote "$manifest_post_date")"
printf 'manifest_post_time=%s\n' "$(shell_quote "$manifest_post_time")"
printf 'manifest_post_slug=%s\n' "$(shell_quote "$manifest_post_slug")"
} > "$manifest_meta_file"
if load_manifest_dir_entry "$manifest_dir_rel"; then
:
else
dir_md_count=0
dir_first_md=""
dir_has_index="false"
fi
dir_md_count=$((dir_md_count + 1))
if [ -z "$dir_first_md" ]; then
dir_first_md="$manifest_rel_path"
fi
if [ "$manifest_filename" = "index.md" ]; then
dir_has_index="true"
fi
write_manifest_dir_meta "$manifest_dir_rel" "$dir_md_count" "$dir_first_md" "$dir_has_index"
printf '%s\n' "$manifest_rel_path" >> "$manifest_all_list"
done
if [ -s "$manifest_hidden_dirs_list" ]; then
LC_ALL=C sort -u "$manifest_hidden_dirs_list" > "$manifest_hidden_dirs_list.sorted"
mv "$manifest_hidden_dirs_list.sorted" "$manifest_hidden_dirs_list"
fi
eval "find \"$src\" \( $IGNORE_ARGS -o $HIDE_ARGS -o $PRESERVE_ARGS \) -prune -o -name \"*.md\" -print" | sort | while IFS= read -r visible_file; do
visible_rel_path="${visible_file#"$src"/}"
load_manifest_entry "$visible_rel_path" || continue
[ "$manifest_draft" = "true" ] && continue
manifest_dir_hidden_by_draft_index "$manifest_dir_rel" && continue
printf '%s\n' "$visible_rel_path" >> "$manifest_visible_list"
done
}

105
lib/metadata.sh Normal file
View File

@@ -0,0 +1,105 @@
#!/bin/sh
parse_frontmatter() {
_fm_file="$1"
_fm_out="$KEWT_TMPDIR/fm_vals.txt"
: > "$_fm_out"
awk -v fm_out="$_fm_out" -f "$awk_dir/frontmatter.awk" "$_fm_file" > /dev/null
fm_title=""
fm_date=""
fm_draft=""
fm_description=""
fm_content_warning=""
fm_tags=""
while IFS='=' read -r _fk _fv; do
case "$_fk" in
title) fm_title="$_fv" ;;
date) fm_date="$_fv" ;;
draft) fm_draft="$_fv" ;;
description) fm_description="$_fv" ;;
content_warning) fm_content_warning="$_fv" ;;
tags) fm_tags="$_fv" ;;
esac
done < "$_fm_out"
rm -f "$_fm_out"
}
strip_markdown_text() {
printf '%s' "$1" | sed \
-e 's/\[//g' \
-e 's/\]//g' \
-e 's/!//g' \
-e 's/\*//g' \
-e 's/_//g' \
-e 's/`//g' \
-e 's/([^)]*)//g' \
-e 's/\\//g'
}
first_heading_from_markdown() {
grep -m 1 '^# ' "$1" | sed 's/^# *//; s/ *$//'
}
markdown_title_from_loaded_file() {
_title_file="$1"
_title_default="$2"
markdown_title="$fm_title"
if [ -z "$markdown_title" ]; then
markdown_title=$(first_heading_from_markdown "$_title_file")
if [ -n "$markdown_title" ]; then
markdown_title=$(strip_markdown_text "$markdown_title")
fi
fi
if [ -z "$markdown_title" ]; then
basename_no_ext=$(basename "$_title_file" .md)
if [ "$basename_no_ext" != "index" ] && [ "$basename_no_ext" != "404_gen" ]; then
markdown_title=$(echo "$basename_no_ext" | awk '{print toupper(substr($0,1,1)) substr($0,2)}')
else
markdown_title="$_title_default"
fi
fi
}
markdown_title_from_file() {
_title_file="$1"
_title_default="$2"
parse_frontmatter "$_title_file"
markdown_title_from_loaded_file "$_title_file" "$_title_default"
}
set_post_datetime() {
_raw_date="$1"
_fallback_name="$2"
if [ -n "$_raw_date" ]; then
post_date=$(echo "$_raw_date" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time=""
if echo "$_raw_date" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$_raw_date" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}[ T_-]\?\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
return
fi
post_date=$(echo "$_fallback_name" | sed 's/^\([0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}\).*/\1/')
post_time="00:00"
if echo "$_fallback_name" | grep -q '^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}'; then
post_time=$(echo "$_fallback_name" | sed 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-\([0-9]\{2\}[:\-][0-9]\{2\}\).*/\1/' | tr '-' ':')
fi
}
set_post_metadata() {
_post_file="$1"
_default_title="$2"
_basename_no_ext=$(basename "$_post_file" .md)
markdown_title_from_file "$_post_file" "$_default_title"
post_heading="$markdown_title"
set_post_datetime "$fm_date" "$_basename_no_ext"
post_slug=$(echo "$_basename_no_ext" | sed \
-e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}-[0-9]\{2\}[:\-][0-9]\{2\}//' \
-e 's/^[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}//' \
-e 's/^[_\-]//')
}

193
lib/runtime.sh Normal file
View File

@@ -0,0 +1,193 @@
#!/bin/sh
trim_whitespace() {
printf '%s' "$1" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}
encode_url_path() {
printf '%s' "$1" | sed \
-e 's/%/%25/g' \
-e 's/ /%20/g' \
-e 's/#/%23/g' \
-e 's/?/%3F/g' \
-e 's/"/%22/g' \
-e "s/'/%27/g"
}
markdown_file_url() {
_rel_path="$1"
printf '/%s.html\n' "$(encode_url_path "${_rel_path%.md}")"
}
directory_index_url() {
_rel_dir="$1"
if [ -z "$_rel_dir" ] || [ "$_rel_dir" = "." ]; then
printf '/index.html\n'
else
printf '/%s/index.html\n' "$(encode_url_path "$_rel_dir")"
fi
}
format_rfc2822_utc() {
_rfc_date="$1"
_rfc_time="${2:-00:00}"
[ -n "$_rfc_time" ] || _rfc_time="00:00"
awk -v d="$_rfc_date" -v t="$_rfc_time" '
function weekday(y, m, day, k, j, h) {
if (m < 3) {
m += 12
y--
}
k = y % 100
j = int(y / 100)
h = (day + int((13 * (m + 1)) / 5) + k + int(k / 4) + int(j / 4) + 5 * j) % 7
return (h + 6) % 7
}
BEGIN {
split(d, da, "-")
split(t, ti, ":")
year = da[1] + 0
month = da[2] + 0
day = da[3] + 0
hour = (ti[1] == "" ? 0 : ti[1]) + 0
minute = (ti[2] == "" ? 0 : ti[2]) + 0
months[1] = "Jan"; months[2] = "Feb"; months[3] = "Mar"; months[4] = "Apr"
months[5] = "May"; months[6] = "Jun"; months[7] = "Jul"; months[8] = "Aug"
months[9] = "Sep"; months[10] = "Oct"; months[11] = "Nov"; months[12] = "Dec"
days[0] = "Sun"; days[1] = "Mon"; days[2] = "Tue"; days[3] = "Wed"
days[4] = "Thu"; days[5] = "Fri"; days[6] = "Sat"
printf "%s, %02d %s %04d %02d:%02d:00 +0000\n",
days[weekday(year, month, day)], day, months[month], year, hour, minute
}
'
}
append_find_rule() {
_expr="$1"
_rule="$2"
if [ -n "$_expr" ]; then
printf '%s -o %s\n' "$_expr" "$_rule"
else
printf '%s\n' "$_rule"
fi
}
append_pattern_rules_from_file() {
_expr="$1"
_root="$2"
_file="$3"
while IFS= read -r line || [ -n "$line" ]; do
case "$line" in
''|'#'*) continue ;;
esac
pattern=$(trim_whitespace "$line")
[ -z "$pattern" ] && continue
pattern_clean="${pattern#/}"
pattern_clean="${pattern_clean%/}"
if echo "$pattern" | grep -q "/"; then
_expr=$(append_find_rule "$_expr" "-path '$_root/$pattern_clean'")
_expr=$(append_find_rule "$_expr" "-path '$_root/$pattern_clean/*'")
else
_expr=$(append_find_rule "$_expr" "-name '$pattern_clean'")
fi
done < "$_file"
printf '%s\n' "$_expr"
}
append_nested_marker_rules() {
_expr="$1"
_root="$2"
_marker="$3"
_tmp_file="$KEWT_TMPDIR/${_marker#*.}_paths"
find "$_root" -name "$_marker" > "$_tmp_file"
while IFS= read -r marker_path; do
marker_dir="${marker_path%/"$marker"}"
if [ "$marker_dir" != "$_root" ] && [ "$marker_dir" != "." ]; then
_expr=$(append_find_rule "$_expr" "-path '$marker_dir'")
_expr=$(append_find_rule "$_expr" "-path '$marker_dir/*'")
fi
done < "$_tmp_file"
rm -f "$_tmp_file"
printf '%s\n' "$_expr"
}
build_rule_args() {
_root="$1"
_marker="$2"
_base_expr="$3"
_expr="$_base_expr"
if [ -f "$_root/$_marker" ]; then
_expr=$(append_pattern_rules_from_file "$_expr" "$_root" "$_root/$_marker")
fi
_expr=$(append_nested_marker_rules "$_expr" "$_root" "$_marker")
printf '%s\n' "$_expr"
}
resolve_template_path() {
template="$src/template.html"
[ -f "$template" ] || template="./template.html"
if [ ! -f "$template" ]; then
template="$KEWT_TMPDIR/default_template.html"
printf '%s\n' "$DEFAULT_TMPL" > "$template"
fi
}
build_full_nav() {
nav=$(generate_nav "$src")
extra_links=$(nav_links_html)
if [ -n "$extra_links" ]; then
nav="$nav
$extra_links"
fi
if [ -n "$nav_extra" ]; then
nav="$nav
$nav_extra"
fi
}
refresh_build_context() {
reset_config
load_config "./site.conf"
load_config "$src/site.conf"
HIDE_ARGS="$BASE_HIDE_ARGS"
if [ -n "$posts_dir" ]; then
HIDE_ARGS=$(append_find_rule "$HIDE_ARGS" "-path '$src/$posts_dir/*'")
fi
asset_version=""
if [ "$versioning" = "true" ]; then
asset_version="?v=$(date '+%Y%m%d%H%M%S')"
fi
resolve_template_path
}
watch_for_changes() {
_mark_file="$1"
changed="$(find "$src" -type f -newer "$_mark_file" 2>/dev/null | head -n 1)"
[ -z "$changed" ] && [ -f "site.conf" ] && [ "site.conf" -nt "$_mark_file" ] && changed="site.conf"
[ -z "$changed" ] && [ -f "$src/site.conf" ] && [ "$src/site.conf" -nt "$_mark_file" ] && changed="$src/site.conf"
[ -z "$changed" ] && [ -f "$template" ] && [ "$template" -nt "$_mark_file" ] && changed="$template"
[ -z "$changed" ] && [ -d "$script_dir/styles" ] && changed="$(find "$script_dir/styles" -type f -newer "$_mark_file" 2>/dev/null | head -n 1)"
printf '%s\n' "$changed"
}
is_posts_directory_rel() {
_rel_dir="$1"
[ -n "$posts_dir" ] && { [ "$_rel_dir" = "$posts_dir" ] || [ "./$_rel_dir" = "$posts_dir" ]; }
}

View File

@@ -15,7 +15,8 @@ sed_inplace() {
fi fi
} }
temp_file="${KEWT_TMPDIR:-/tmp}/markdown.$$.md" temp_parent="${KEWT_TMPDIR:-${TMPDIR:-/tmp}}"
temp_file="${temp_parent}/markdown.$$.md"
cat "$@" > "$temp_file" cat "$@" > "$temp_file"
trap 'rm -f "$temp_file" "$temp_file.tmp" "$temp_file.fm"' EXIT INT TERM trap 'rm -f "$temp_file" "$temp_file.tmp" "$temp_file.fm"' EXIT INT TERM
@@ -30,7 +31,7 @@ awk -f "$awk_dir/mask_inline_code.awk" "$temp_file" > "$temp_file.tmp" && mv "$t
awk -f "$awk_dir/mask_plain.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" awk -f "$awk_dir/mask_plain.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
# Reference links # Reference links
refs=$(cat "$@" | awk '/^\[[^\]]+\]: */') refs=$(awk '/^\[[^\]]+\]: */' "$temp_file")
IFS=' IFS='
' '
for ref in $refs; do for ref in $refs; do
@@ -75,7 +76,7 @@ awk -f "$awk_dir/breaks.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.t
awk -f "$awk_dir/paragraphs.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" awk -f "$awk_dir/paragraphs.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
# Inline styles # Inline styles
awk -f "$awk_dir/emoji.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" awk -v emoji_file="$awk_dir/emoji.tsv" -f "$awk_dir/emoji.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -f "$awk_dir/markdown_inline.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file" awk -f "$awk_dir/markdown_inline.awk" "$temp_file" > "$temp_file.tmp" && mv "$temp_file.tmp" "$temp_file"
awk -v input_file="$1" -v site_root="$MARKDOWN_SITE_ROOT" -v fallback_file="$MARKDOWN_FALLBACK_FILE" -v script_dir="$script_dir" -f "$awk_dir/markdown_embed.awk" "$temp_file" awk -v input_file="$1" -v site_root="$MARKDOWN_SITE_ROOT" -v fallback_file="$MARKDOWN_FALLBACK_FILE" -v script_dir="$script_dir" -f "$awk_dir/markdown_embed.awk" "$temp_file"
rm "$temp_file" rm "$temp_file"

View File

@@ -43,7 +43,7 @@ search_in_header = false
include_cw_pages_in_search = false include_cw_pages_in_search = false
``` ```
- `title` - site title - `title` - site title
- `style` - style file name from `./styles` (without `.css`) - `style` - style name from the built-in `styles/` directory. See [Theming](theming.md)
- `lang` - document language, used for the `<html lang="...">` attribute (default: "en") - `lang` - document language, used for the `<html lang="...">` attribute (default: "en")
- `draft_by_default` - default value for the `draft` frontmatter field in new posts created (default: false) - `draft_by_default` - default value for the `draft` frontmatter field in new posts created (default: false)
- `dir_indexes` - generate directory index pages when missing `index.md` - `dir_indexes` - generate directory index pages when missing `index.md`
@@ -62,7 +62,7 @@ include_cw_pages_in_search = false
- `favicon` - explicit favicon path (used when `logo_as_favicon` is false or no logo is set) - `favicon` - explicit favicon path (used when `logo_as_favicon` is false or no logo is set)
- `generate_page_title` - automatically generate title text from the first markdown heading or filename (default: true) - `generate_page_title` - automatically generate title text from the first markdown heading or filename (default: true)
- `error_page` - filename for the generated 404 error page (default: "not_found.html", empty to disable) - `error_page` - filename for the generated 404 error page (default: "not_found.html", empty to disable)
- `versioning` - append a version query parameter (`?v=timestamp`) to css asset urls to bypass cache (default: false) - `versioning` - append a build-time version query parameter (for example `?v=20260505193210`) to css asset urls to bypass cache (default: false)
- `enable_header_links` - turns markdown section headings into clickable anchor links (default: true) - `enable_header_links` - turns markdown section headings into clickable anchor links (default: true)
- `base_url` - absolute URL of the site, used for sitemap and RSS feed generation - `base_url` - absolute URL of the site, used for sitemap and RSS feed generation
- `generate_feed` - enable RSS feed generation (requires `base_url`) - `generate_feed` - enable RSS feed generation (requires `base_url`)
@@ -75,7 +75,7 @@ include_cw_pages_in_search = false
- `tags_dir` - directory name for generated tag pages (default: "tags") - `tags_dir` - directory name for generated tag pages (default: "tags")
- `generate_search` - generate a `search.json` index for client-side search - `generate_search` - generate a `search.json` index for client-side search
- `search_in_footer` - include a search box in the page footer (requires `generate_search`) - `search_in_footer` - include a search box in the page footer (requires `generate_search`)
- `search_in_header` - include a search box in the page header (requires `generate_search`) - `search_in_header` - include a search box in the page header (requires `generate_search` and a template that includes `{{HEADER_SEARCH}}`)
- `include_cw_pages_in_search` - include content warning pages in the search index (default: false) - `include_cw_pages_in_search` - include content warning pages in the search index (default: false)
## Dot Files ## Dot Files

View File

@@ -13,6 +13,14 @@ title = "Embeds"
If you want to **force** a file to be inlined, use `\!![]` instead of `\![]` If you want to **force** a file to be inlined, use `\!![]` instead of `\![]`
## Reality-Breaking Embeds
`\!![link]` and `\!![alt](link)` work even inside inline code blocks. If the content between backticks consists only of `\!![]` embeds, the embed triggers and the content is inlined instead of being rendered as code.
```
`!![/file.sh]`
```
## Typed Embeds ## Typed Embeds
Force specific output regardless of extension: Force specific output regardless of extension:

View File

@@ -38,4 +38,83 @@ This renders as `<dl><dt>Term</dt><dd>Definition</dd></dl>`. Multiple definition
## Emoji Shortcodes ## Emoji Shortcodes
Standard GitHub/MkDocs emoji shortcodes like `:smile:`, `:fire:`, `:rocket:` are automatically replaced with their Unicode emoji equivalents. Shortcodes inside code blocks are left as-is. Standard GitHub/MkDocs emoji shortcodes like `:smile:`, `:fire:`, `:rocket:` are automatically replaced with their Unicode emoji equivalents. Shortcodes inside codeblocks are left as-is.
## Pipe Tables
Tables use the GitHub-style syntax:
```md
| Header 1 | Header 2 |
|---|---|
| cell 1 | cell 2 |
| cell 3 | cell 4 |
```
Column alignment is set with colons in the separator:
```md
| Left | Center | Right |
|:---|:---:|---:|
| a | b | c |
```
Tables can drop the header row and with a separator:
```md
|---|---|
| a | b |
```
## Blockquotes
Standard Markdown blockquote syntax using `>`:
```md
> This is a blockquote.
> It can span multiple lines.
```
### Admonitions
Blockquotes that start with a type tag become styled admonition blocks. Five built-in types are supported: `NOTE`, `TIP`, `IMPORTANT`, `WARNING`, `CAUTION`.
```md
> [!NOTE]
> This is a note admonition.
```
Custom admonition types can be added via the `custom_admonitions` config option in `site.conf`.
## Task Lists
GFM-style task lists are supported inside **both** ordered and unordered lists:
```md
- [ ] Unchecked item
- [x] Checked item
- Normal item
1. [ ] Unchecked item
2. [x] Checked item
3. Normal item
```
## Reference Links
Markdown reference-style links and images are supported:
```md
[link text][ref]
[ref]: https://example.com "Optional title"
![alt text][img-ref]
[img-ref]: /image.png "Optional title"
```
## Plain Text Blocks
Content inside `<plain>...</plain>` tags is rendered without any Markdown processing
## MFM Font Syntax
Misskey-style font syntax is supported for inline font family changes:
- `$[font.serif text]` - serif font
- `$[font.mono text]` - monospace font
- `$[font.sans text]` - sans-serif font

View File

@@ -13,7 +13,10 @@ When customizing `template.html`, the placeholders available are:
- `{{LANG}}` - the configured document language - `{{LANG}}` - the configured document language
- `{{HEAD_EXTRA}}` - meta-tags - `{{HEAD_EXTRA}}` - meta-tags
- `{{HEADER_BRAND}}` - header rendering the name and/or logo - `{{HEADER_BRAND}}` - header rendering the name and/or logo
- `{{HEADER_SEARCH}}` - header search form when `search_in_header = true`
## Search ## Search
When `generate_search` is enabled, kewt embeds a search bar into pages based on the `search_in_header` and `search_in_footer` config options. The search uses a `search.json` index generated at build time and a client-side JS script. No external dependencies are required. When `generate_search` is enabled, kewt embeds a search bar into pages based on the `search_in_header` and `search_in_footer` config options. The search uses a `search.json` index generated at build time and a client-side JS script. No external dependencies are required.
If you use a custom template and want header search enabled, make sure your `template.html` includes `{{HEADER_SEARCH}}` somewhere inside `<header>`.

62
site/docs/theming.md Normal file
View File

@@ -0,0 +1,62 @@
---
title = "Theming"
---
# Theming
*kewt* has a few colour palettes built-in. Set the `style` option in `site.conf` to a theme name to apply it.
## Built-in Themes
| Theme | `style` value | Dark/Light |
|---|---|---|
| Kewt (default) | `kewt` | Light |
| Kewt Light | `kewt-light` | Light |
| Nord | `nord` | Dark |
| Nord Light | `nord-light` | Light |
| Monokai | `monokai` | Dark |
| Monokai Light | `monokai-light` | Light |
| Grayscale | `grayscale` | Dark |
| Grayscale Light | `grayscale-light` | Light |
| One Dark | `onedark` | Dark |
| One Light | `onelight` | Light |
| Rose Pine | `rosepine` | Dark |
| Rose Pine Light | `rosepine-light` | Light |
| Solarized | `solarized` | Light |
| Solarized Dark | `solarized-dark` | Dark |
```conf
style = "kewt-light"
```
## How It Works
Each theme is a `.root.css` file containing a `:root` block with CSS custom properties. At build time, *kewt* merges the theme's variables with the base `kewt.css` stylesheet. The base `:root` block is stripped out and replaced with the theme's variables.
## Style Resolution
*kewt* resolves styles in this priority order (highest wins):
1. `site/styles.css` - a full custom stylesheet in your site directory. Overrides everything.
2. `site/styles.root.css` - custom `:root` variables merged with the built-in `kewt.css` base.
3. built-in `<style>.css` - a full stylesheet matching the `style` config value.
4. built-in `<style>.root.css` - `:root` variables merged with `kewt.css`.
If none of these exist, the unmodified `kewt.css` is used
## Custom Themes
To create a custom colour theme, place a `styles.root.css` file in your site directory. The file should contain only a `:root` block with the CSS variables you want to override:
```css
:root {
--bg: #1a1b26;
--fg: #c0caf5;
--fg-link: #7aa2f7;
--fg-heading: #c0caf5;
--code-bg: #24283b;
}
```
Any variables not overridden will fall back to the defaults in `kewt.css`. The `:root` block in the base stylesheet is automatically removed to prevent conflicts.
## Per-Directory Styles
Subdirectories can have their own `styles.css` or `styles.root.css` that apply only to pages in that directory. Per-directory styles follow the same priority.

View File

@@ -20,4 +20,6 @@ kewt --serve [port]
- `--generate-template [path]` writes the default `template.html` to the given path (defaults to `template.html` in the current directory). - `--generate-template [path]` writes the default `template.html` to the given path (defaults to `template.html` in the current directory).
- `--update [dir]` adds any missing keys to `site.conf` and checks `template.html` against the latest default. - `--update [dir]` adds any missing keys to `site.conf` and checks `template.html` against the latest default.
- `--watch` (`-w`) watches for file changes in the source directory and rebuilds automatically. - `--watch` (`-w`) watches for file changes in the source directory and rebuilds automatically.
- `--serve` (`-s`) starts a local HTTP server (python3 or busybox) in the output directory after building. Use with the port number to specify the port. Composable with `--watch`. - `--clean` cleans the output directory before building (default behavior).
- `--no-clean` does not clean the output directory before building. Useful with `--watch` to avoid clearing output on every rebuild.
- `--serve` (`-s`) starts a local HTTP server (python3 or busybox) in the output directory after building. Use with the port number to specify the port. The default port is `8000`. Composable with `--watch`.

View File

@@ -1,12 +1,9 @@
--- ---
title = "Heaven" title = "Heaven"
content_warning = "This page may contain CSS" content_warning = "This page may have bad effects on people with an allergy to cats"
--- ---
# Heaven # Heaven
Told you Told you
Probably should've mentioned the catgirl too ![catgirl.jpg]
| --- | --- |
| ```![/styles.css]``` | <img style="vertical-align: top;" src="catgirl.jpg"> |

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #aaaa78; --adm-warning-border: #aaaa78;
--adm-caution-bg: #eee0e0; --adm-caution-bg: #eee0e0;
--adm-caution-border: #aa8888; --adm-caution-border: #aa8888;
--thead-bg: #e8e8e8;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #8a8a5a; --adm-warning-border: #8a8a5a;
--adm-caution-bg: #2e1a1a; --adm-caution-bg: #2e1a1a;
--adm-caution-border: #8a5a5a; --adm-caution-border: #8a5a5a;
--thead-bg: #111111;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #b8860b; --adm-warning-border: #b8860b;
--adm-caution-bg: #f5dddd; --adm-caution-bg: #f5dddd;
--adm-caution-border: #c44569; --adm-caution-border: #c44569;
--thead-bg: #e8dffa;
} }

View File

@@ -23,6 +23,7 @@
--adm-warning-border: #ffe2bd; --adm-warning-border: #ffe2bd;
--adm-caution-bg: #662d43; --adm-caution-bg: #662d43;
--adm-caution-border: #ffc4d5; --adm-caution-border: #ffc4d5;
--thead-bg: #3d2d5c;
} }
body { body {
@@ -36,6 +37,11 @@ body {
} }
header { header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
padding: 20px; padding: 20px;
padding-bottom: 0; padding-bottom: 0;
border-bottom: 1px solid var(--code-border); border-bottom: 1px solid var(--code-border);
@@ -44,6 +50,8 @@ header {
header h1 { header h1 {
margin: 0; margin: 0;
flex: 1 1 auto;
min-width: 0;
font-size: 35px; font-size: 35px;
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
@@ -113,6 +121,12 @@ a {
margin-left: -10px; margin-left: -10px;
} }
#side-bar a.current-page:hover,
#side-bar a.current-page:focus {
color: var(--fg);
background: transparent;
}
a:hover { a:hover {
background: var(--fg); background: var(--fg);
color: var(--bg); color: var(--bg);
@@ -130,6 +144,50 @@ h3 {
font-weight: normal; font-weight: normal;
} }
.header-link {
color: inherit;
display: inline;
}
.header-link:hover,
.header-link:focus {
background: transparent;
color: inherit;
}
.header-anchor {
opacity: 0;
margin-left: 0.25em;
transition: opacity 0.15s ease;
}
h1:hover > .header-anchor,
h2:hover > .header-anchor,
h3:hover > .header-anchor,
h4:hover > .header-anchor,
h5:hover > .header-anchor,
h6:hover > .header-anchor,
h1:focus-within > .header-anchor,
h2:focus-within > .header-anchor,
h3:focus-within > .header-anchor,
h4:focus-within > .header-anchor,
h5:focus-within > .header-anchor,
h6:focus-within > .header-anchor,
h1:hover .header-link .header-anchor,
h2:hover .header-link .header-anchor,
h3:hover .header-link .header-anchor,
h4:hover .header-link .header-anchor,
h5:hover .header-link .header-anchor,
h6:hover .header-link .header-anchor,
h1:focus-within .header-link .header-anchor,
h2:focus-within .header-link .header-anchor,
h3:focus-within .header-link .header-anchor,
h4:focus-within .header-link .header-anchor,
h5:focus-within .header-link .header-anchor,
h6:focus-within .header-link .header-anchor {
opacity: 1;
}
pre { pre {
background: var(--code-bg); background: var(--code-bg);
color: var(--code-fg); color: var(--code-fg);
@@ -271,6 +329,32 @@ hr {
border-top: 1px solid var(--code-border); border-top: 1px solid var(--code-border);
} }
table {
border-collapse: collapse;
margin: 20px 0;
width: 100%;
}
thead {
border-bottom: 2px solid var(--code-border);
background: var(--thead-bg);
}
th {
font-weight: bold;
text-align: left;
padding: 8px 12px;
}
td {
padding: 8px 12px;
border-top: 1px solid var(--code-border);
}
tr:nth-child(even) {
background: var(--bg-deep);
}
.nav-toggle, .nav-toggle,
.nav-toggle-label { .nav-toggle-label {
display: none; display: none;
@@ -303,7 +387,7 @@ hr {
margin: 0 20px 20px 20px; margin: 0 20px 20px 20px;
} }
.nav-toggle:checked~#side-bar { .nav-toggle:checked ~ #side-bar {
display: block; display: block;
} }
@@ -428,9 +512,10 @@ hr {
.kewt-search-header { .kewt-search-header {
display: inline-flex; display: inline-flex;
gap: 4px; gap: 4px;
margin-left: auto; margin-left: 0;
vertical-align: middle; vertical-align: middle;
font-style: normal; font-style: normal;
flex: 0 0 auto;
} }
.kewt-search-header input[type="text"] { .kewt-search-header input[type="text"] {
@@ -536,4 +621,4 @@ hr {
flex: 1; flex: 1;
width: auto; width: auto;
} }
} }

View File

@@ -0,0 +1,27 @@
:root {
--bg: #fafaf8;
--bg-deep: #f0efe8;
--fg: #272822;
--fg-muted: #8f8e84;
--fg-link: #5c8a1e;
--fg-heading: #272822;
--code-bg: #f0efe8;
--code-border: #cccbc2;
--code-fg: #272822;
--code-sel: #5c8a1e;
--code-prop: #c41e6a;
--code-val: #7a3ee0;
--code-var: #2e8faf;
--code-com: #8f8e84;
--adm-note-bg: #edf4f7;
--adm-note-border: #2e8faf;
--adm-tip-bg: #eef7ed;
--adm-tip-border: #5c8a1e;
--adm-important-bg: #f2eef7;
--adm-important-border: #7a3ee0;
--adm-warning-bg: #f7f5ed;
--adm-warning-border: #b0a01e;
--adm-caution-bg: #f7edee;
--adm-caution-border: #c41e6a;
--thead-bg: #f0efe8;
}

27
styles/monokai.root.css Normal file
View File

@@ -0,0 +1,27 @@
:root {
--bg: #272822;
--bg-deep: #1e1f1c;
--fg: #f8f8f2;
--fg-muted: #75715e;
--fg-link: #a6e22e;
--fg-heading: #f8f8f2;
--code-bg: #1e1f1c;
--code-border: #49483e;
--code-fg: #f8f8f2;
--code-sel: #a6e22e;
--code-prop: #f92672;
--code-val: #ae81ff;
--code-var: #66d9ef;
--code-com: #75715e;
--adm-note-bg: #272822;
--adm-note-border: #66d9ef;
--adm-tip-bg: #272822;
--adm-tip-border: #a6e22e;
--adm-important-bg: #272822;
--adm-important-border: #ae81ff;
--adm-warning-bg: #272822;
--adm-warning-border: #e6db74;
--adm-caution-bg: #272822;
--adm-caution-border: #f92672;
--thead-bg: #1e1f1c;
}

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #ebcb8b; --adm-warning-border: #ebcb8b;
--adm-caution-bg: #eddcdc; --adm-caution-bg: #eddcdc;
--adm-caution-border: #bf616a; --adm-caution-border: #bf616a;
--thead-bg: #d8dee9;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #ebcb8b; --adm-warning-border: #ebcb8b;
--adm-caution-bg: #3b3840; --adm-caution-bg: #3b3840;
--adm-caution-border: #bf616a; --adm-caution-border: #bf616a;
--thead-bg: #3b4252;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #e5c07b; --adm-warning-border: #e5c07b;
--adm-caution-bg: #3a2c2e; --adm-caution-bg: #3a2c2e;
--adm-caution-border: #e06c75; --adm-caution-border: #e06c75;
--thead-bg: #21252b;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #986801; --adm-warning-border: #986801;
--adm-caution-bg: #fae8e8; --adm-caution-bg: #fae8e8;
--adm-caution-border: #e45649; --adm-caution-border: #e45649;
--thead-bg: #eaeaeb;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #ea9d34; --adm-warning-border: #ea9d34;
--adm-caution-bg: #f5e5e8; --adm-caution-bg: #f5e5e8;
--adm-caution-border: #b4637a; --adm-caution-border: #b4637a;
--thead-bg: #f2e9e1;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #f6c177; --adm-warning-border: #f6c177;
--adm-caution-bg: #2a1f22; --adm-caution-bg: #2a1f22;
--adm-caution-border: #eb6f92; --adm-caution-border: #eb6f92;
--thead-bg: #1f1d2e;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #b58900; --adm-warning-border: #b58900;
--adm-caution-bg: #360a07; --adm-caution-bg: #360a07;
--adm-caution-border: #cb4b16; --adm-caution-border: #cb4b16;
--thead-bg: #073642;
} }

View File

@@ -23,4 +23,5 @@
--adm-warning-border: #b58900; --adm-warning-border: #b58900;
--adm-caution-bg: #fde8e8; --adm-caution-bg: #fde8e8;
--adm-caution-border: #dc322f; --adm-caution-border: #dc322f;
--thead-bg: #eee8d5;
} }