导航栏

Home

  • wss://relay.damus.io
  • wss://nostr-pub.wellorder.net
  • wss://nostr.mom
  • wss://nostr.slothy.win
  • wss://relay.stoner.com
  • wss://nostr.einundzwanzig.space
  • wss://nos.lol
  • wss://relay.nostr.band
  • wss://no.str.cr
  • wss://nostr-relay.schnitzel.world
  • wss://relay.nostr.com.au
  • wss://knostr.neutrine.com
  • wss://nostr.nodeofsven.com
  • wss://nostr.vulpem.com
  • wss://nostr-verif.slothy.win
  • wss://relay.lexingtonbitcoin.org
  • wss://nostr-1.nbo.angani.co
  • wss://relay.wellorder.net
  • wss://nostr.easydns.ca
  • wss://relay.dwadziesciajeden.pl
  • wss://relay.orangepill.dev
  • wss://e.nos.lol
  • wss://ragnar-relay.com
  • wss://nostr.data.haus
  • wss://nostr.wine
  • wss://nostr.koning-degraaf.nl
  • wss://nostr.thank.eu
  • wss://relay.hamnet.io
  • wss://nostr.sidnlabs.nl
  • wss://nostr.inosta.cc
  • wss://nostr21.com
  • wss://nostr.ch3n2k.com
  • wss://relay.nostrview.com
  • wss://relay.nostromo.social
  • wss://offchain.pub
  • wss://relay.nostr.wirednet.jp
  • wss://nostr.l00p.org
  • wss://lightningrelay.com
  • wss://bitcoinmaximalists.online
  • wss://private.red.gb.net
  • wss://relay.nostrid.com
  • wss://relay.nostrcheck.me
  • wss://nostrelay.yeghro.site
  • wss://nostrue.com
  • wss://nostr.topeth.info
  • wss://nostr.bitcoiner.social
  • wss://nostr.spaceshell.xyz
  • wss://nostr.roundrockbitcoiners.com
  • wss://relay.nostrology.org
  • wss://nostr-dev.wellorder.net
  • wss://relay.snort.social
  • wss://nr.yay.so
  • wss://nostr.lu.ke
  • wss://atlas.nostr.land
  • wss://nostr.pjv.me
  • wss://brb.io
  • wss://eden.nostr.land
  • wss://nostr-verified.wellorder.net
  • wss://nostr.noones.com
  • wss://relay.nostr.nu
  • wss://nostr-relay.bitcoin.ninja
  • wss://paid.no.str.cr
  • wss://freespeech.casa
  • wss://bitcoiner.social
  • wss://nostr.1f52b.xyz
  • wss://nostr.sebastix.dev
  • wss://nostr.cizmar.net
  • wss://n.wingu.se
  • wss://nostr.bitcoinplebs.de
  • wss://nostr.corebreach.com
  • wss://nostr.tools.global.id
  • wss://xmr.usenostr.org
  • wss://at.nostrworks.com
  • wss://nostr.sovbit.host
  • wss://nostr.1sat.org
  • wss://mastodon.cloud/api/v1/streaming
  • wss://nostr.256k1.dev
  • wss://relay.beta.fogtype.com
  • wss://rsslay.ch3n2k.com
  • wss://relay.nostr.bg
  • wss://nostr.primz.org
  • wss://relay.johnnyasantos.com
  • wss://btc.klendazu.com
  • wss://slick.mjex.me
  • wss://nostr.yael.at
  • wss://nostr.lorentz.is
  • wss://relay.nostrified.org
  • wss://nostr.orangepill.dev
  • wss://relay.primal.net
  • wss://nostr.cercatrova.me
  • wss://nostr.swiss-enigma.ch
  • wss://nostr-relay.derekross.me
  • wss://puravida.nostr.land
  • wss://nostr.sectiontwo.org
  • wss://nostr.oxtr.dev
  • wss://relay.s3x.social
  • wss://relay.ryzizub.com
  • wss://nostr.liberty.fans
  • wss://nostr.frostr.xyz
  • wss://nostr.ginuerzh.xyz
  • wss://nostr.d11n.net
  • wss://nostr.semisol.dev
  • wss://misskey.io
  • wss://nostr.namek.link
  • wss://nostr.gruntwerk.org
  • wss://relay.nostr.wf
  • wss://nostr.land
  • wss://relay.mostr.pub
  • wss://relay.nostrplebs.com
  • wss://purplepag.es
  • wss://yestr.me
  • wss://relay.nostr.ai
  • wss://paid.nostrified.org
  • wss://nostr-02.dorafactory.org
  • wss://nostr.zbd.gg
  • wss://relay.hodl.ar
  • wss://relay.nostr.sc
  • wss://feeds.nostr.band/nostrhispano
  • wss://nostr.middling.mydns.jp
  • wss://nostr.portemonero.com
  • wss://search.nos.today
  • wss://relay.minds.com/nostr/v1/ws
  • wss://welcome.nostr.wine
  • wss://yabu.me
  • wss://nrelay.c-stellar.net
  • wss://nostrja-kari.heguro.com
  • wss://nostr-relay.app
  • wss://rly.nostrkid.com
  • wss://nostr.filmweb.pl
  • wss://relay.utxo.one
  • wss://nostr.strits.dk
  • wss://relay.poster.place
  • wss://nostr.mining.sc
  • wss://nostr.xmr.rocks
  • wss://ithurtswhenip.ee
  • wss://powrelay.xyz
  • wss://relay.vanderwarker.family
  • wss://wc1.current.ninja
  • wss://nostr.ingwie.me
  • wss://nostr.gleeze.com
  • wss://relay.nostrify.io
  • wss://relay2.nostrchat.io
  • wss://relay1.nostrchat.io
  • wss://relay.devstr.org
  • wss://relay.nostr.hu
  • wss://nostr.hifish.org
  • wss://nostr2.sanhauf.com
  • wss://nostrja-kari-nip50.heguro.com
  • wss://nostr.rikmeijer.nl
  • wss://nostrua.com
  • wss://relay.nsecbunker.com
  • wss://nostr.hekster.org
  • wss://nostr.schorsch.fans
  • wss://nostr.reelnetwork.eu
  • wss://nostr.hexhex.online
  • wss://relay.wavlake.com
  • wss://relay.nostr.lighting
  • wss://nostr.sagaciousd.com
  • wss://nostr.fbxl.net
  • wss://multiplextr.coracle.social
  • wss://nostril.cam
  • wss://nostr.btc-library.com
  • wss://relay.getalby.com/v1
  • wss://rss.nos.social
  • wss://nostr.overmind.lol
  • wss://relay.nostrcn.com
  • wss://nostr-01.yakihonne.com
  • wss://nostr.fort-btc.club
  • wss://relay.bitcoinpark.com
  • wss://nostr01.counterclockwise.io
  • wss://relap.orzv.workers.dev
  • wss://christpill.nostr1.com
  • wss://relay.verified-nostr.com
  • wss://nostr.sathoarder.com
  • wss://wbc.nostr1.com
  • wss://nostr.is-defs.fun
  • wss://nostr.heliodex.cf
  • wss://nostr.4liberty.one
  • wss://f7z.io
  • wss://relay.casualcrypto.date
  • wss://relay.notmandatory.org
  • wss://fiatjaf.com
  • wss://relay.despera.space
  • wss://bitstack.app
  • wss://nostr-relay.psfoundation.info
  • wss://purplerelay.com
  • wss://relay.orangepill.ovh
  • wss://nostr.rubberdoll.cc
  • wss://relay.ingwie.me
  • wss://soloco.nl
  • wss://nostr.dlsouza.lol
  • wss://relay.kamp.site
  • wss://nostr.heavyrubberslave.com
  • wss://relay.keychat.io
  • wss://relay.froth.zone
  • wss://nostr.bitcoinist.org
  • wss://nostr.cloud.vinney.xyz
  • wss://relay.momostr.pink
  • wss://nosdrive.app/relay
  • wss://nostrrelay.win
  • wss://nostr.8777.ch
  • wss://unhostedwallet.com
  • wss://21ideas.nostr1.com
  • wss://nostr.stakey.net
  • wss://nostr.sats.li
  • wss://relay.guggero.org
  • wss://relay.noswhere.com
  • wss://unostr.site
  • wss://pyramid.fiatjaf.com
  • wss://frens.nostr1.com
  • wss://creatr.nostr.wine
  • wss://140.f7z.io
  • wss://a.nos.lol
  • wss://directory.yabu.me
  • wss://hist.nostr.land
  • wss://nostr.dodge.me.uk
  • wss://privateisland.club
  • wss://relay.weloveit.info
  • wss://relay.magiccity.live
  • wss://nostr.notribe.net
  • wss://relay.westernbtc.com
  • wss://relay.siamstr.com
  • wss://groups.0xchat.com
  • wss://theforest.nostr1.com
  • wss://problematic.network
  • wss://us.purplerelay.com
  • wss://nostr1.daedaluslabs.io
  • wss://relay.noderunners.network
  • wss://relay.azzamo.net
  • wss://bevo.nostr1.com
  • wss://freelay.sovbit.host
  • wss://relay.sebdev.io
  • wss://inbox.nostr.wine
  • wss://ditto.puhcho.me/relay
  • wss://nostr.t-rg.ws
  • wss://multiplexer.huszonegy.world
  • wss://nostr.huszonegy.world
  • wss://nostr.jcloud.es
  • wss://nostr.thurk.org
  • wss://dev-relay.kube.b-n.space
  • wss://relay.nos.social
  • wss://ditto.slothy.win/relay
  • wss://nostr.hashi.sbs
  • wss://paid.relay.vanderwarker.family
  • wss://nostr.javi.space
  • wss://gleasonator.dev/relay
  • wss://bucket.coracle.social
  • wss://relay.nsec.app
  • wss://relay.sepiropht.me
  • wss://nostr.kungfu-g.rip
  • wss://hotrightnow.nostr1.com
  • wss://relay.artx.market
  • wss://nfrelay.app
  • wss://relay.notoshi.win
  • wss://relay.highlighter.com
  • wss://relay.geyser.fund
  • wss://relay.minibits.cash
  • wss://nostr.petrkr.net/strfry
  • wss://n.ok0.org
  • wss://greensoul.space
  • wss://nostr-02.yakihonne.com
  • wss://nostr-03.dorafactory.org
  • wss://vitor.nostr1.com
  • wss://relay.lax1dude.net
  • wss://relay.zhoushen929.com
  • wss://relay.oke.minds.io/nostr/v1/ws
  • wss://strfry.corebreach.com
  • wss://relay.bitdevs.tw
  • wss://nostr.btczh.tw
  • wss://nostrich.adagio.tw
  • wss://nostr.zoel.network
  • wss://nostr.lifeonbtc.xyz
  • wss://nostr.se7enz.com
  • wss://thecitadel.nostr1.com
  • wss://mleku.nostr1.com
  • wss://nostr2.daedaluslabs.io
  • wss://fiatjaf.nostr1.com
  • wss://nostr.daedaluslabs.io
  • wss://xmr.ithurtswhenip.ee
  • wss://cellar.nostr.wine
  • wss://rkgk.moe
  • wss://nostr.self-determined.de
  • wss://nostr.gerbils.online
  • wss://jingle.carlos-cdb.top
  • wss://carlos-cdb.top
  • wss://bostr.online
  • wss://relays.diggoo.com
  • wss://jp.purplerelay.com
  • wss://ir.purplerelay.com
  • wss://me.purplerelay.com
  • wss://nostr.0x7e.xyz
  • wss://nostr.reckless.dev
  • wss://relay.nostr.net
  • wss://relay.fountain.fm
  • wss://v1250.planz.io/nostr
  • wss://relay.13room.space
  • wss://relay.usefusion.ai
  • wss://ae.purplerelay.com
  • wss://njump.me
  • wss://au.purplerelay.com
  • wss://in.purplerelay.com
  • wss://nosflare.plebes.fans
  • wss://nostr.at
  • wss://bostr.bitcointxoko.com
  • wss://nostria.space
  • wss://nostr.searx.is
  • wss://test.nfrelay.app
  • wss://relay.fanfares.io
  • wss://relay.varke.eu
  • wss://relay.nostr.jabber.ch
  • wss://relay.nostpy.lol
  • wss://relay.camelus.app
  • wss://nostr.myshosholoza.co.za
  • wss://nostr.ussenterprise.xyz
  • wss://nostr.dbtc.link
  • wss://ftp.halifax.rwth-aachen.de/nostr
  • wss://tw.purplerelay.com
  • wss://eu.purplerelay.com
  • wss://relay.benthecarman.com
  • wss://fabian.nostr1.com
  • wss://staging.yabu.me
  • wss://nostr.dmgd.monster
  • wss://testnet.plebnet.dev/nostrrelay/1
  • wss://hivetalk.nostr1.com
  • wss://relay.lawallet.ar
  • wss://relay.piazza.today
  • wss://relay.pleb.to
  • wss://orangepiller.org
  • wss://nostr.lopp.social
  • wss://ch.purplerelay.com
  • wss://loli.church
  • wss://adre.su
  • wss://misskey.04.si
  • wss://cache2.primal.net/v1
  • wss://relay.sincensura.org
  • wss://relay.moinsen.com
  • wss://relay.freeplace.nl
  • wss://cache1.primal.net/v1
  • wss://nostr.openordex.org
  • wss://rly.bopln.com
  • wss://relay.0v0.social
  • wss://nostr.psychoet.nexus
  • wss://relay.nostrr.de
  • wss://us.nostr.land
  • wss://srtrelay.c-stellar.net
  • wss://lnbits.papersats.io/nostrclient/api/v1/relay
  • wss://strfry.chatbett.de
  • wss://relay.nquiz.io
  • wss://nostr.plantroon.com
  • wss://nostr.jfischer.org
  • wss://relay.corpum.com
  • wss://relay.bostr.online
  • wss://lunchbox.sandwich.farm
  • wss://nr.rosano.ca
  • wss://nostr.happytavern.co
  • wss://novoa.nagoya
  • wss://misskey.takehi.to
  • wss://relay.satoshidnc.com
  • wss://nostr.novacisko.cz
  • wss://nsrelay.assilvestrar.club
  • wss://nostr.atitlan.io
  • wss://relay.livefreebtc.dev
  • wss://nostr.tavux.tech
  • wss://nostr.girino.org
  • wss://merrcurrup.railway.app
  • wss://nostr-dev.zbd.gg
  • wss://submarin.online
  • wss://social.camph.net
  • wss://relay.nostrich.cc
  • wss://relay.lumina.rocks
  • wss://profiles.nostr1.com
  • wss://rebelbase.social/relay
  • wss://support.nostr1.com
  • wss://relay.dev.bdw.to
  • wss://ca.purplerelay.com
  • wss://nostr.bitcoinvn.io
  • wss://auth.nostr1.com
  • wss://custom.fiatjaf.com
  • wss://hub.nostr-relay.app
  • wss://nostr.babyshark.win
  • wss://echo.websocket.org
  • wss://nostr.kosmos.org
  • wss://polnostr.xyz
  • wss://relay.refinery.coracle.tools
  • wss://user.kindpag.es
  • wss://nostr.hashbang.nl
  • wss://czas.live
  • wss://chorus.pjv.me
  • wss://relay.agorist.space
  • wss://bostr.cx.ms
  • wss://relay.nostrhub.fr
  • wss://groups.fiatjaf.com
  • wss://hodlbod.coracle.tools
  • wss://hk.purplerelay.com
  • wss://lnbits.satoshibox.io/nostrclient/api/v1/relay
  • wss://relay.cosmicbolt.net
  • wss://nostr.drafted.pro
  • wss://nostr.a2x.pub
  • wss://bostr.lightningspore.com
  • wss://nostr.intrepid18.com
  • wss://de.purplerelay.com
  • wss://obiurgator.thewhall.com
  • wss://nostr.madco.me
  • wss://relay.braydon.com
  • wss://nostr-relay.algotech.io
  • wss://relay.unknown.cloud
  • wss://relay.gems.xyz
  • wss://longhorn.bgp.rodeo
  • wss://notes.miguelalmodo.com
  • wss://onlynotes.lol
  • wss://relay.tagayasu.xyz
  • wss://relay.zone667.com
  • wss://nostr-relay.sn-media.com
  • wss://relay.mostro.network
  • wss://nostr.polyserv.xyz
  • wss://n3r.xyz
  • wss://relay5.bitransfer.org
  • wss://brisceaux.com
  • wss://nostr.faust.duckdns.org
  • wss://satellite.hzrd149.com
  • wss://gnost.faust.duckdns.org
  • wss://relay.jerseyplebs.com
  • wss://nostr.cxplay.org
  • wss://nostr.polonkai.eu
  • wss://libretechsystems.nostr1.com
  • wss://nostr.pailakapo.com
  • wss://relay.alex71btc.com
  • wss://cfrelay.puhcho.workers.dev
  • wss://kiwibuilders.nostr21.net
  • wss://nostr3.daedaluslabs.io
  • wss://relay1.xfire.to:
  • wss://nostr.brackrat.com
  • wss://relay.satlantis.io
  • wss://relay.test.nquiz.io
  • wss://relay.illuminodes.com
  • wss://relay.arrakis.lat
  • wss://cfrelay.haorendashu.workers.dev
  • wss://core.btcmap.org/nostrrelay/relay
  • wss://junxingwang.org
  • wss://relay2.angor.io
  • wss://relaypag.es
  • wss://nostr.skitso.business
  • wss://history.nostr.watch
  • wss://relay.oh-happy-day.xyz
  • wss://invillage-outvillage.com
  • wss://nostr-relay.cbrx.io
  • wss://tigs.nostr1.com
  • wss://misskey.design
  • wss://relay.nostrainsley.coracle.tools
  • wss://relay.cxplay.org
  • wss://relay.angor.io
  • wss://nostr.tbai.me:592
  • wss://strfry.iris.to
  • wss://orangesync.tech
  • wss://nostr.chaima.info
  • wss://relay.minibolt.info
  • wss://jingle.nostrver.se
  • wss://kr.purplerelay.com
  • wss://fl.purplerelay.com
  • wss://relay.chontit.win
  • wss://nostr.bilthon.dev
  • wss://relay.vengeful.eu
  • wss://dtonon.nostr1.com
  • wss://relay.gasteazi.net
  • wss://us.nostr.wine
  • wss://frjosh.nostr1.com
  • wss://relay.staging.geyser.fund
  • wss://nostr.yuhr.org
  • wss://relay.mattybs.lol
  • wss://sushi.ski
  • wss://relay.unsupervised.online
  • wss://nostr.bit4use.com
  • wss://prl.plus
  • wss://news.nos.social
  • wss://airchat.nostr1.com
  • wss://nortis.nostr1.com
  • wss://adeptus.cwharton.com
  • wss://relay.mycelium.social
  • wss://node.coincreek.com/nostrclient/api/v1/relay
  • wss://nostr2.girino.org
  • wss://relay.s-w.art
  • wss://nerostr.girino.org
  • wss://uk.purplerelay.com
  • wss://bostr.erechorse.com
  • wss://eostagram.com
  • wss://relay.coinos.io
  • wss://sendit.nosflare.com
  • wss://relay.nostraddress.com
  • wss://nostrelites.org
  • wss://wot.nostr.party
  • wss://wot.utxo.one
  • wss://haven.cyberhornet.net
  • wss://relay.customkeys.de
  • wss://relay.groups.nip29.com
  • wss://relay29.notoshi.win
  • wss://nostr.2h2o.io
  • wss://relay.nostriot.com
  • wss://relay.lem0n.cc
  • wss://relay.b1t.beer
  • wss://rocky.nostr1.com
  • wss://wot.sovbit.host
  • wss://relay.sovereign.app
  • wss://zap.watch
  • wss://zorrelay.libretechsystems.xyz
  • wss://sorrelay.libretechsystems.xyz
  • wss://mailbox.mw.leastauthority.com/v1
  • wss://memrelay.girino.org
  • wss://relay.lnfi.network
  • wss://wot.girino.org
  • wss://labour.fiatjaf.com
  • wss://wot.codingarena.top
  • wss://relay.nostrdice.com
  • wss://nostr.azzamo.net
  • wss://wot.azzamo.net
  • wss://wot.nostr.sats4.life
  • wss://wot.nostr.net
  • wss://api.freefrom.space/v1/ws
  • wss://wheat.happytavern.co
  • wss://chorus.bonsai.com
  • wss://strfry.bonsai.com
  • wss://wot.sebastix.social
  • wss://inner.sebastix.social
  • wss://haven.accioly.social
  • wss://relay.notestack.com
  • wss://relay.sigit.io
  • wss://satsage.xyz
  • wss://nostr.noderunners.network
  • wss://chronicle.puhcho.me
  • wss://haven.puhcho.me
  • wss://haven.calva.dev/inbox
  • wss://dergigi.nostr1.com
  • wss://wons.calva.dev
  • wss://thebarn.nostr1.com
  • wss://nostr.grooveix.com
  • wss://relay.rodbishop.nz/inbox
  • wss://travis-shears-nostr-relay-v2.fly.dev
  • wss://nostr.sprovoost.nl
  • wss://nostr.x0f.org
  • wss://bostr.syobon.net
  • wss://art.nostrfreaks.com
  • wss://cobrafuma.com/relay
  • wss://alru07.nostr1.com
  • wss://relay.nostrfreedom.net/outbox
  • wss://aplaceinthesun.nostr1.com
  • wss://relay.flirtingwithbitcoin.com
  • wss://plebone.nostr1.com
  • wss://cfrelay.snowcait.workers.dev
  • wss://thewritingdesk.nostr1.com
  • wss://relay.botev.sv
  • wss://relay.degmods.com
  • wss://seth.nostr1.com
  • wss://untreu.me
  • wss://agentorange.nostr1.com
  • wss://reimagine.nostr1.com
  • wss://nostr.takasaki.dev
  • wss://nostr.coincrowd.fund
  • wss://bnc.netsec.vip
  • wss://nostr.community.ath.cx
  • wss://nostr.cltrrd.us
  • wss://relay.xeble.me
  • wss://no.netsec.vip
  • wss://strfry.shock.network
  • wss://relay.8333.space
  • wss://relay02.lnfi.network
  • wss://relay.lightning.gdn
  • wss://nostr.cypherpunk.today
  • wss://relay.nostrfreaks.com
  • wss://relay.shuymn.me
  • wss://haven.eternal.gdn
  • wss://cyberspace.nostr1.com
  • wss://nostr-rs-relay.dev.fedibtc.com
  • wss://relay.das.casa
  • wss://ursin.nostr1.com
  • wss://monitorlizard.nostr1.com
  • wss://wot.shaving.kiwi
  • wss://relay.cyphernomad.com
  • wss://nostr.extrabits.io
  • wss://relay.jellyfish.land
  • wss://wot.tealeaf.dev
  • wss://chorus.tealeaf.dev
  • wss://haven.tealeaf.dev/inbox
  • wss://h.codingarena.top/inbox
  • wss://relay.goodmorningbitcoin.com
  • wss://wot.zacoos.com
  • wss://relay.shawnyeager.com/chat
  • wss://proxy0.siamstr.com
  • wss://articles.layer3.news
  • wss://relay.hs.vc
  • wss://chronicle.dtonon.com
  • wss://wot.dtonon.com
  • wss://relay.stens.dev
  • wss://social.protest.net/relay
  • wss://relay.patrickulrich.com/inbox
  • wss://relay.davidebtc.me
  • wss://relay.dev.ntech.it
  • wss://chronicle.dev.ntech.it
  • wss://nostr.bitpunk.fm
  • wss://lnvoltz.com/nostrrelay/n49jzjytb
  • wss://ghost.dolu.dev
  • wss://thebarn.nostrfreaks.com
  • wss://niel.nostr1.com
  • wss://adoringcardinal1.lnbits.com/nostrrelay/test-relay
  • wss://nostr.thebiglake.org
  • wss://wot.relay.vanderwarker.family
  • wss://haven.girino.org
  • wss://pow.hzrd149.com
  • wss://nostr-news.nostr1.com
  • wss://thewildhustle.nostr1.com
  • wss://nostr.sats.coffee
  • wss://dikaios1517.nostr1.com
  • wss://nostr4.daedaluslabs.io
  • wss://relay.calders.us
  • wss://nostr.mikoshi.de
  • wss://relay.nuts.cash
  • wss://brightlights.nostr1.com
  • wss://darknights.nostr1.com
  • wss://relay.chrisatmachine.com
  • wss://nostr.agentcampfire.com
  • wss://nostr.me/relay
  • wss://relay.nostr.watch
  • wss://dwebcamp.nos.social
  • wss://nostr.1312.media
  • wss://nostr.phauna.org
  • wss://henhouse.social/relay
  • wss://nostr.neilalexander.dev
  • wss://nip13.girino.org
  • wss://tijl.xyz
  • wss://relay.rengel.org
  • wss://relay.stewlab.win
  • wss://relay.badgr.digital
  • wss://relay.crbl.io
  • wss://rl.baud.one
  • wss://relay.axeldolce.xyz
  • wss://nip85.nostr.band
  • wss://antisocial.nostr1.com
  • wss://relay.isphere.lol
  • wss://cl4.tnix.dev
  • wss://nostr.dl3.dedyn.io
  • wss://nostr.camalolo.com
  • wss://hayloo.nostr1.com
  • wss://nostr.schneimi.de
  • wss://wostr.hexhex.online
  • wss://pareto.nostr1.com
  • wss://nostr.pareto.space
  • wss://relay.utih.net
  • wss://relay.lifpay.me
  • wss://david.nostr1.com
  • wss://haven.ciori.net
  • wss://bonifatius.nostr1.com
  • wss://pay.thefockinfury.wtf/nostrrelay/1
  • wss://relay.xplbzx.uk
  • wss://nostr.tac.lol
  • wss://btcpay2.nisaba.solutions/nostr
  • wss://relay.bitcoinschool.nl
  • wss://dev-relay.lnfi.network
  • wss://relay.netstr.io
  • wss://freespeech.social/relay
  • wss://straylight.cafe/relay
  • wss://nostr-relay01.redscrypt.org:48443
  • wss://relay.devvul.com
  • wss://relay.bitcoinveneto.org
  • wss://relay.shop21.dk
  • wss://nostr.mtrj.cz
  • wss://relay.jthecodemonkey.xyz
  • wss://devapi.freefrom.space/v1/ws
  • wss://aaa-api.freefrom.space/v1/ws
  • wss://nostr.rosenbaum.se
  • wss://fido-news.z7.ai
  • wss://stratum.libretechsystems.xyz
  • wss://tamby.mjex.me
  • wss://nostr.cottongin.xyz
  • wss://wot.eminence.gdn
  • wss://hi.myvoiceourstory.org
  • wss://nostr.red5d.dev
  • wss://relay-testnet.k8s.layer3.news
  • wss://nostr.pistaum.com
  • wss://relay-nwc.rizful.com/v1
  • wss://nostrum.satoshinakamoto.win
  • wss://eupo43gj24.execute-api.us-east-1.amazonaws.com/test
  • wss://backup.keychat.io
  • wss://relay.openbalance.app
  • wss://nostr.jonmartins.com
  • wss://social.proxymana.net
  • wss://nostr-pr02.redscrypt.org
  • wss://nostr-pr03.redscrypt.org
  • wss://nostrelay.memory-art.xyz
  • wss://promenade.fiatjaf.com
  • wss://inbox.azzamo.net
  • wss://premium.primal.net
  • wss://nostr.timegate.co
  • wss://team-relay.pareto.space
  • wss://relay.dariccoin.me
  • wss://relay.dannymorabito.com/inbox
  • wss://nostr.lojong.info
  • wss://relay.transtoad.com
  • wss://cfrelay.royalgarter.workers.dev
  • wss://nostr-rs-relay-ishosta.phamthanh.me
  • wss://nostr.rblb.it:7777
  • wss://fiatrevelation.nostr1.com
  • wss://sources.nostr1.com
  • wss://nostr-pr04.redscrypt.org
  • wss://relay.nostronautti.fi
  • wss://moonboi.nostrfreaks.com
  • wss://mats-techno-gnome-ca.trycloudflare.com
  • wss://nostr.d3id.xyz/relay
  • wss://nostr.holbrook.no
  • wss://logen.btcforplebs.com
  • wss://relay.nostrtalk.org
  • wss://community.proxymana.net
  • wss://misskey.gothloli.club
  • wss://mleku.realy.lol
  • wss://relay.maiqr.app
  • wss://relay.tv-base.com
  • wss://relay.rkus.se
  • wss://relay.snotr.nl:49999
  • wss://relay.brightbolt.net/inbox
  • wss://magic.nostr1.com
  • wss://null.spdns.eu
  • wss://nostr.itdestro.cc
  • wss://nostrrelay.taylorperron.com
  • wss://nostr.tegila.com.br
  • wss://stage.mosavi.xyz/v1/ws
  • wss://eclipse.pub/relay
  • wss://relay.asthroughfire.com
  • wss://relay.nostrarabia.com
  • wss://news.utxo.one
  • wss://relay.stream.labs.h3.se
  • wss://mls.akdeniz.edu.tr/nostr
  • wss://asia.azzamo.net
  • wss://tollbooth.stens.dev
  • wss://nostrelay.yeghro.com
  • wss://nostrich.zonemix.tech
  • wss://relay.chakany.systems
  • wss://45.135.180.104
  • wss://relay.mwaters.net
  • wss://kitchen.zap.cooking
  • wss://basedpotato.nostr1.com
  • wss://nostr1.jpegslangah.com
  • wss://primus.nostr1.com
  • wss://wot.mwaters.net
  • wss://prod.mosavi.io/v1/ws
  • wss://bots.utxo.one
  • wss://nostr.caramboo.com
  • wss://9yo.punipoka.pink
  • wss://nostr.trepechov.com
  • wss://stg.nostpy.lol
  • wss://nostr.ovia.to
  • wss://riley.timegate.co
  • wss://willow.timegate.co
  • wss://relay.olas.app
  • wss://social.olsentribe.fyi
  • wss://relay.arx-ccn.com
  • wss://zaplab.nostr1.com
  • wss://hax.reliefcloud.com
  • wss://nostrrelay.blocktree.cc
  • wss://hole.v0l.io
  • wss://nostr.phuture.sk
  • wss://cc3d.nostr1.com
  • wss://coop.nostr1.com
  • wss://synalysis.nostr1.com
  • wss://nostr.luisschwab.net
  • wss://relay.fr13nd5.com
  • wss://relay.nostrfy.io
  • wss://relay.vaporware.network
  • wss://relay.mzm.app
  • wss://nostr.felixzieger.de
  • wss://nostr.spicyz.io
  • wss://relay.pre-alfa.iz-collaborator.com
  • wss://bunker.vanderwarker.family
  • wss://ltgnetwork.nostr1.com
  • wss://relay.bullishbounty.com
  • wss://dev.coracle.tools
  • wss://relay04.lnfi.network
  • wss://jellyfish.land
  • wss://relay03.lnfi.network
  • wss://rtson.onrender.com
  • wss://nostr.1sat.store
  • wss://v2.fly.dev
  • wss://communities.nos.social
  • wss://vidono.apps.slidestr.net
  • wss://nostrboss.com
  • wss://aegis.utxo.one
  • wss://data.relay.vanderwarker.family
  • wss://relay.marc26z.com
  • wss://relay-dev.netstr.io
  • wss://relay.danieldaquino.me
  • wss://aegis.relayted.de
  • wss://relay.nostrverified.fyi
  • wss://n.posto.us.kg
  • wss://relay.hook.cafe
  • wss://aegis.relaynostr.xyz
  • wss://nostrapps.com
  • wss://nostr.douglascruz.com.br
  • wss://wot.relayted.de
  • wss://inbox.mycelium.social
  • wss://relay.d11n.net
  • wss://haven.relayted.de
  • wss://free.relayted.de
  • wss://relay.zapstore.dev
  • wss://chat.mihhdu.org
  • wss://bridge.duozhutuan.com
  • wss://relay.evanverma.com
  • wss://relay.nostrdvm.com
  • wss://nostr.joomaen.top
  • wss://nostr.235421.xyz
  • wss://nostr.bitcoinsult.de
  • wss://nostrelay.circum.space
  • wss://relay1.plor.dev
  • wss://relayrs.notoshi.win
  • wss://fenrir-s.notoshi.win
  • wss://bostr.azzamo.net
  • wss://nos.zct-mrl.com
  • wss://skeme.vanderwarker.family
  • @ 89391693:ae75dc0d
    2025-05-11 23:20:07
    commit d642ea7b0367ed895e88d10113889a65c17aa4cc Author: randymcmillan Date: Wed Apr 9 15:58:09 2025 -0400 v0.0.39 diff --git a/.justfile b/.justfile new file mode 100644 index 0000000..4616260 --- /dev/null +++ b/.justfile @@ -0,0 +1,63 @@ +default: + just --choose + +help: + @make help + +all: + @make all + +bin: + @make bin + +cargo-help: + @make cargo-help + +cargo-release-all: + @make cargo-release-all + +cargo-clean-release: + @make cargo-clean-release + +cargo-publish-all: + @make cargo-publish-all + +cargo-install-bins: + @make cargo-install-bins + +cargo-build: + @make cargo-build + +cargo-install: + @make cargo-install + +cargo-build-release: + @make cargo-build-release + +cargo-check: + @make cargo-check + +cargo-bench: + @make cargo-bench + +cargo-test: + @make cargo-test + +cargo-test-nightly: + @make cargo-test-nightly + +cargo-report: + @make cargo-report + +cargo-run: + @make cargo-run + +cargo-dist: + @make cargo-dist + +cargo-dist-build: + @make cargo-dist-build + +cargo-dist-manifest: + @make cargo-dist-manifest + diff --git a/.nojekyll b/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/Cargo.lock b/Cargo.lock index bf9c2ac..8f567b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -883,7 +883,7 @@ dependencies = [ [[package]] name = "nips" -version = "0.0.38" +version = "0.0.39" dependencies = [ "axum", "bytes", diff --git a/Cargo.toml b/Cargo.toml index fca211d..b302a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "nips" -version = "0.0.38" +version = "0.0.39" edition = "2021" authors = ["randymcmillan ", "The nostr-protocol/nips Contributors"] repository = "https://github.com/gnostr-org/gnostr-nips.git" diff --git a/Makefile b/Makefile index 7ad80b5..04b9b3f 100644 --- a/Makefile +++ b/Makefile @@ -12,17 +12,6 @@ all: bin### all bin: ### bin cargo b --manifest-path Cargo.toml -.ONESHELL: -readme-html: - @cargo b && ./target/debug/nips -s README > readme.html - if [ ! $(shell uname -s) = 'Darwin' ]; then \ - if grep -q Microsoft /proc/version; then \ - alias open='explorer.exe'; \ - else \ - alias open='xdg-open'; \ - fi \ - fi \ - && open readme.html ## ##=============================================================================== ##make cargo-* diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..4761c5c --- /dev/null +++ b/USAGE.md @@ -0,0 +1,23 @@ +## nips +## nips +## nips +## nips +## nips +## nips + +nips: a nostr-protocol/nips server + +Usage: nips [OPTIONS] + +Options: + -d, --debug Enable debug logging + -l, --list-embedded List all embedded files + -s, --show Show the contents of an embedded file using a pager + --serve Axum Serve + -p, --port Sets the port number to listen on [default: 8080] + -e, --export Export all embedded files to the current directory + --export-html Export all embedded files to the current directory + --export-path Export all embedded files to the specified path + -u, --usage Show this manual + -h, --help Print help + -V, --version Print version diff --git a/docs/USAGE.html b/docs/USAGE.html new file mode 100644 index 0000000..38fa87d --- /dev/null +++ b/docs/USAGE.html @@ -0,0 +1,20 @@ +

    nips

    +

    nips

    +

    nips

    +

    nips

    +

    nips

    +

    nips

    +

    nips: a nostr-protocol/nips server

    +

    Usage: nips [OPTIONS]

    +

    Options: +-d, --debug Enable debug logging +-l, --list-embedded List all embedded files +-s, --show Show the contents of an embedded file using a pager +--serve Axum Serve +-p, --port Sets the port number to listen on [default: 8080] +-e, --export Export all embedded files to the current directory +--export-html Export all embedded files to the current directory +--export-path Export all embedded files to the specified path +-u, --usage Show this manual +-h, --help Print help +-V, --version Print version

    diff --git a/examples/export-all.rs b/examples/export-all.rs deleted file mode 100644 index 0911087..0000000 --- a/examples/export-all.rs +++ /dev/null @@ -1,377 +0,0 @@ -use clap::Parser; -use rust_embed::Embed; -use std::env; -use std::fs; -use std::fs::File; -use std::io; -use std::io::{stdout, Write}; -use std::os::unix::fs::PermissionsExt; -use std::path::{Path, PathBuf}; -use std::process::Command; -use std::process::Stdio; -use termimad::crossterm::{ - cursor::{Hide, Show}, - event::{self, Event, KeyCode::*, KeyEvent}, - queue, - style::Color::*, - terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, -}; -use termimad::*; -use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter, Registry}; - -#[derive(Embed)] -#[folder = "."] -#[include = "*.md"] -#[exclude = "*.DS_Store"] -#[exclude = "target/*"] -#[exclude = "src"] -#[exclude = "src/*"] -#[exclude = ".git"] -#[exclude = ".git/*"] -#[exclude = ".github/*"] -#[exclude = ".gitignore"] -#[exclude = ".justfile"] -#[exclude = ".nojekyll"] -#[exclude = "build.rs"] -#[exclude = "dist-workspace.toml"] -#[exclude = "error.log"] -#[exclude = "output.log"] -#[exclude = "post-commit-history"] -#[exclude = "script.sh"] -#[exclude = "template/Makefile"] -#[exclude = "template/default_config.conf"] -#[exclude = "template/install_script.sh"] -#[exclude = "test_files/tabbed.txtbuild.rs"] -#[exclude = "Cargo.lock"] -#[exclude = "Cargo.toml"] -#[exclude = "LICENSE"] -#[exclude = "Makefile"] -struct Template; - -/// A simple tool to view embedded Markdown files. -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// Enable debug logging. - #[clap(short, long)] - debug: bool, - - /// List all embedded files. - #[clap(short, long)] - list_embedded: bool, - - /// Show the contents of an embedded file using a pager. - #[clap(short, long, value_name = "NIP")] - show: Option, - - /// Export all embedded files to the current directory. - #[clap(long)] - export: bool, -} - -fn make_executable(script_path: &Path) -> io::Result<()> { - let mut permissions = fs::metadata(script_path)?.permissions(); - permissions.set_mode(permissions.mode() | 0o111); - fs::set_permissions(script_path, permissions)?; - tracing::debug!("Made '{}' executable.", script_path.display()); - Ok(()) -} - -fn execute_script(script_path: &Path) -> io::Result<()> { - tracing::debug!("Executing script: {}", script_path.display()); - let log_file = File::create("output.log")?; - let error_file = File::create("error.log")?; - let mut command = Command::new(script_path); - command - .stdout(Stdio::from(log_file)) - .stderr(Stdio::from(error_file)); - let status = command.spawn()?.wait()?; - if status.success() { - tracing::debug!("Script '{}' executed successfully.", script_path.display()); - Ok(()) - } else { - eprintln!( - "Script '{}' failed with exit code: {:?}", - script_path.display(), - status.code() - ); - Err(io::Error::new( - io::ErrorKind::Other, - format!( - "Script execution failed with exit code: {:?}", - status.code() - ), - )) - } -} - -fn canonicalize_path(path: &Path) -> io::Result { - let absolute_path = if path.is_relative() { - let current_dir = env::current_dir()?; - current_dir.join(path) - } else { - path.to_path_buf() - }; - fs::canonicalize(absolute_path) -} - -fn extract(filename: &str, output_dir: &Path) -> io::Result<()> { - match Template::get(filename) { - Some(embedded_file) => { - let output_path = output_dir.join(filename); - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent)?; - } - let mut outfile = File::create(&output_path)?; - outfile.write_all(embedded_file.data.as_ref())?; - tracing::debug!( - "Successfully exported '{}' to '{}'", - filename, - output_path.display() - ); - Ok(()) - } - None => Err(io::Error::new( - io::ErrorKind::NotFound, - format!("Embedded file '{}' not found!", filename), - )), - } -} - -fn view_area() -> Area { - let mut area = Area::full_screen(); - area.pad_for_max_width(120); - area -} - -fn run_app(skin: MadSkin, nip: String) -> Result<(), Error> { - let mut w = stdout(); - queue!(w, EnterAlternateScreen)?; - terminal::enable_raw_mode()?; - queue!(w, Hide)?; - let mut view = MadView::from(nip.to_owned(), view_area(), skin); - loop { - view.write_on(&mut w)?; - w.flush()?; - match event::read() { - Ok(Event::Key(KeyEvent { code, .. })) => match code { - Up => view.try_scroll_lines(-1), - Down => view.try_scroll_lines(1), - PageUp => view.try_scroll_pages(-1), - PageDown => view.try_scroll_pages(1), - Char('q') | Esc => break, - _ => {} - }, - Ok(Event::Resize(..)) => { - queue!(w, Clear(ClearType::All))?; - view.resize(&view_area()); - } - _ => {} - } - } - terminal::disable_raw_mode()?; - queue!(w, Show)?; - queue!(w, LeaveAlternateScreen)?; - w.flush()?; - Ok(()) -} - -fn make_skin() -> MadSkin { - let mut skin = MadSkin::default(); - skin.table.align = Alignment::Center; - skin.set_headers_fg(AnsiValue(178)); - skin.bold.set_fg(Yellow); - skin.italic.set_fg(Magenta); - skin.scrollbar.thumb.set_fg(AnsiValue(178)); - skin.code_block.align = Alignment::Center; - skin -} - -fn main() -> Result<(), Box> { - let args = Args::parse(); - - let level_filter = if args.debug { - EnvFilter::new("debug") - } else { - EnvFilter::from_default_env() - }; - - let subscriber = Registry::default() - .with(fmt::layer().with_writer(std::io::stdout)) - .with(level_filter); - tracing::subscriber::set_global_default(subscriber) - .expect("Failed to set global default subscriber"); - - tracing::debug!("Parsed arguments: {:?}", args); - - if args.list_embedded { - tracing::debug!("Embedded files:"); - for file in Template::iter() { - println!("{}", file.as_ref()); - } - return Ok(()); - } - - if args.export { - tracing::info!("Exporting all embedded files to the current directory..."); - let current_dir = env::current_dir()?; - let mut export_count = 0; - for file in Template::iter() { - match extract(file.as_ref(), ¤t_dir) { - Ok(_) => { - export_count += 1; - } - Err(e) => { - eprintln!("Error exporting '{}': {}", file.as_ref(), e); - } - } - } - tracing::info!("Successfully exported {} embedded files.", export_count); - return Ok(()); - } - - if let Some(nip_arg) = &args.show { - let filename = if nip_arg.ends_with(".md") { - nip_arg.clone() - } else { - format!("{:02}.md", nip_arg) - }; - - match Template::get(&filename) { - Some(embedded_file) => { - let content = String::from_utf8_lossy(embedded_file.data.as_ref()); - let skin = make_skin(); - let _res = run_app(skin, (&content).to_string()); - return Ok(()); - } - None => { - eprintln!("Error: Embedded NIP file '{}' not found!", filename); - std::process::exit(1); - } - } - } - - tracing::debug!( - "Canonical path of '.': {}", - canonicalize_path(Path::new("."))?.display() - ); - tracing::debug!( - "Canonical path of 'src': {}", - canonicalize_path(Path::new("src"))?.display() - ); - - #[cfg(windows)] - let absolute_path_str = "C:\\Windows\\System32\\cmd.exe"; - #[cfg(not(windows))] - let absolute_path_str = "/bin/ls"; - - tracing::debug!( - "Canonical path of '{}': {}", - absolute_path_str, - canonicalize_path(Path::new(absolute_path_str))?.display() - ); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use rust_embed::RustEmbed; - use std::fs; - use std::path::PathBuf; - use std::thread; - use std::time::Duration; - - fn create_test_file(path: &PathBuf, content: &str) -> io::Result<()> { - fs::create_dir_all(path.parent().unwrap())?; - fs::write(path, content)?; - Ok(()) - } - - #[test] - fn test_make_test_file() -> io::Result<()> { - let test_file_content = "Line with\ttab.\nAnother\t\ttabbed line.\n\tLeading tab.\n\\tThis line starts with a literal backslash-t.\nThis line has a tab in the middle: like this."; - let script_path = Path::new("tabbed.txt"); - let test_files = Path::new("test_files"); - let full_path = test_files.join(script_path); - tracing::debug!("{}", full_path.display()); - create_test_file(&full_path, test_file_content)?; - thread::sleep(Duration::from_secs(1)); - Ok(()) - } - - #[test] - fn test_preserve_tabs() -> io::Result<()> { - test_make_test_file()?; - let test_file_content = "Line with\ttab.\nAnother\t\ttabbed line.\n\tLeading tab.\n\\tThis line starts with a literal backslash-t.\nThis line has a tab in the middle: like this."; - - #[derive(RustEmbed)] - #[folder = "./test_files"] - #[exclude = "*.DS_Store"] - struct EmbeddedAssets; - - for file in EmbeddedAssets::iter() { - println!("Found asset: {}", file.as_ref()); - - match EmbeddedAssets::get(file.as_ref()) { - Some(embedded_file) => { - let content = String::from_utf8_lossy(embedded_file.data.as_ref()); - println!("Contents of {}:\n{}", file.as_ref(), content); - let install_default_conf = Command::new("echo") - .arg(content.as_ref()) - .status() - .expect("Failed to execute install script"); - if install_default_conf.success() { - println!("Installation script executed successfully."); - } else { - eprintln!("Installation script failed."); - } - } - None => { - eprintln!("Error: {} not found in embedded assets!", file.as_ref()); - } - } - } - - if let Some(file) = EmbeddedAssets::get("tabbed.txt") { - let embedded_content = String::from_utf8_lossy(file.data.as_ref()).to_string(); - assert_eq!(embedded_content, test_file_content); - assert!(embedded_content.contains("\t")); - assert!(embedded_content.contains(" ")); - assert!(embedded_content.lines().nth(0).unwrap().contains('\t')); - assert!(embedded_content.lines().nth(0).unwrap().contains(" ")); - assert!(embedded_content.lines().nth(1).unwrap().contains('\t')); - assert!(embedded_content.lines().nth(1).unwrap().contains(" ")); - assert!(embedded_content.lines().nth(1).unwrap().contains("\t\t")); - assert!(embedded_content.lines().nth(1).unwrap().contains(" ")); - assert!(embedded_content.lines().nth(2).unwrap().contains('\t')); - assert!(embedded_content.lines().nth(2).unwrap().contains(" ")); - assert!(!embedded_content.lines().nth(3).unwrap().starts_with('\t')); - assert!(!embedded_content.lines().nth(3).unwrap().starts_with("\t")); - assert!(!embedded_content.lines().nth(3).unwrap().starts_with(" ")); - assert!(embedded_content.lines().nth(0).unwrap().contains("h\t")); - assert!(embedded_content.lines().nth(1).unwrap().contains("r\t")); - assert!(embedded_content.lines().nth(2).unwrap().contains("\tL")); - } else { - println!("Failed to embed 'tabbed.txt'"); - } - Ok(()) - } - - #[test] - fn remove_dir_all_custom() -> io::Result<()> { - let path = Path::new("test_files"); - if path.is_dir() { - fs::remove_dir_all(path)?; - fs::create_dir_all(path)?; - } else if path.is_file() { - fs::remove_file(path)?; - fs::create_dir_all(path)?; - } else { - fs::create_dir_all(path)?; - } - test_make_test_file()?; - Ok(()) - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4ca46ec --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,342 @@ +//! run this example with +//! cargo run --example scrollable +//! +use clap::Parser; +use pulldown_cmark::Options; +use pulldown_cmark::{html, Parser as HTMLParser}; +use rust_embed::Embed; + +use sha2::Digest; +use sha2::Sha256; +use std::io; +use std::io::{stdout, Write}; +use std::path::{Path, PathBuf}; + +use termimad::crossterm::{ + cursor::{Hide, Show}, + event::{self, Event, KeyCode::*, KeyEvent}, + queue, + style::Color::*, + terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use termimad::*; +use tokio::fs; +use tokio::fs::File; +use tokio::io::AsyncWriteExt; + +pub mod path; + +#[derive(Embed)] +#[folder = "."] +#[include = "*.md"] +#[exclude = "*.DS_Store"] +#[exclude = "target/*"] +#[exclude = "src"] +#[exclude = "src/*"] +#[exclude = ".git"] +#[exclude = ".git/*"] +#[exclude = ".github/*"] +#[exclude = ".gitignore"] +#[exclude = ".justfile"] +#[exclude = ".nojekyll"] +#[exclude = "build.rs"] +#[exclude = "dist-workspace.toml"] +#[exclude = "error.log"] +#[exclude = "output.log"] +#[exclude = "post-commit-history"] +#[exclude = "script.sh"] +#[exclude = "template/Makefile"] +#[exclude = "template/default_config.conf"] +#[exclude = "template/install_script.sh"] +#[exclude = "test_files/tabbed.txtbuild.rs"] +#[exclude = "Cargo.lock"] +#[exclude = "Cargo.toml"] +#[exclude = "LICENSE"] +#[exclude = "Makefile"] +pub struct Template; + +/// a simple nostr-protocol/nips server +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + /// Enable debug logging. + #[clap(short, long)] + pub debug: bool, + + /// List all embedded files. + #[clap(short, long)] + pub list_embedded: bool, + + /// Show the contents of an embedded file using a pager. + //#[clap(short, long, value_name = "NIP", default_value = "README.md")] + #[clap(short, long, value_name = "NIP")] + pub show: Option, + + /// Axum Serve. + //#[clap(long, default_value = "false")] + #[clap(long, default_value = "false")] + pub serve: bool, + + /// Sets the port number to listen on + #[arg(short, long, value_parser = clap::value_parser!(u16), default_value_t = 8080)] + pub port: u16, + + /// Export all embedded files to the current directory. + #[clap(short, long)] + pub export: bool, + + /// Export all embedded files to the current directory. + #[clap(long)] + pub export_html: bool, + + /// Export all embedded files to the specified path. + #[clap(long, value_name = "PATH")] + pub export_path: Option, + + #[arg(short, long, help = "Show USAGE.md")] + pub usage: bool, +} + +pub fn calculate_sha256(data: &[u8]) -> String { + let mut hasher = Sha256::new(); + hasher.update(data); + format!("{:x}", hasher.finalize()) +} + +pub async fn extract(filename: &str, output_dir: &Path) -> io::Result<()> { + match Template::get(filename) { + Some(embedded_file) => { + let output_path = output_dir.join(filename); + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).await.expect(""); + } + let mut outfile = File::create(&output_path).await?; + outfile.write_all(embedded_file.data.as_ref()).await?; + tracing::trace!( + "Successfully exported '{}' to '{}'", + filename, + output_path.display() + ); + Ok(()) + } + None => Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Embedded file '{}' not found!", filename), + )), + } +} + +pub async fn extract_html(filename: &str, output_dir: &Path) -> io::Result<()> { + match Template::get(filename) { + Some(embedded_file) => { + let output_path = output_dir + .join("docs") + .join(remove_md_extension(filename).to_owned() + ".html"); + if let Some(parent) = output_path.parent() { + fs::create_dir_all(parent).await.expect(""); + } + let mut outfile = File::create(&output_path).await?; + // let embedded_file_data: &'static [u8] = embedded_file.data.as_ref(); + let embedded_file_data: Vec = embedded_file.data.as_ref().to_vec(); // Create an owned Vec + + //std::str::from_utf8(embedded_file_data) + //outfile.write_all(markdown_to_html(&std::str::from_utf8(embedded_file_data).expect("")).as_bytes())?; + outfile + .write_all( + markdown_to_html(&std::str::from_utf8(&embedded_file_data).expect("")) + .as_bytes(), + ) + .await?; + + //outfile.write_all(markdown_to_html(embedded_file_data[0..5])); + tracing::trace!( + "Successfully exported '{}' to '{}'", + filename, + output_path.display() + ); + Ok(()) + } + None => Err(io::Error::new( + io::ErrorKind::NotFound, + format!("Embedded file '{}' not found!", filename), + )), + } +} + +pub fn view_area() -> Area { + let mut area = Area::full_screen(); + area.pad_for_max_width(120); // we don't want a too wide text column + area +} + +pub async fn run_app(skin: MadSkin, nip: String) -> Result<(), Error> { + let mut w = stdout(); // we could also have used stderr + queue!(w, EnterAlternateScreen)?; + terminal::enable_raw_mode()?; + queue!(w, Hide)?; // hiding the cursor + // get nip here + let mut view = MadView::from(nip.to_owned(), view_area(), skin); + loop { + view.write_on(&mut w)?; + w.flush()?; + match event::read() { + Ok(Event::Key(KeyEvent { code, .. })) => match code { + Up => view.try_scroll_lines(-1), + Down => view.try_scroll_lines(1), + PageUp => view.try_scroll_pages(-1), + PageDown => view.try_scroll_pages(1), + _ => break, + }, + Ok(Event::Resize(..)) => { + queue!(w, Clear(ClearType::All))?; + view.resize(&view_area()); + } + _ => {} + } + } + terminal::disable_raw_mode()?; + queue!(w, Show)?; // we must restore the cursor + queue!(w, LeaveAlternateScreen)?; + w.flush()?; + Ok(()) +} + +pub fn make_skin() -> MadSkin { + let mut skin = MadSkin::default(); + skin.table.align = Alignment::Center; + skin.set_headers_fg(AnsiValue(178)); + skin.bold.set_fg(Yellow); + skin.italic.set_fg(Magenta); + skin.scrollbar.thumb.set_fg(AnsiValue(178)); + skin.code_block.align = Alignment::Center; + skin +} + +pub fn markdown_to_html(markdown_input: &str) -> String { + let mut options = Options::empty(); + //options.insert(Options::all()); + options.insert(Options::ENABLE_TABLES); + options.insert(Options::ENABLE_FOOTNOTES); + options.insert(Options::ENABLE_STRIKETHROUGH); + options.insert(Options::ENABLE_TASKLISTS); + //options.insert(Options::ENABLE_SMART_PUNCTUATION); + options.insert(Options::ENABLE_HEADING_ATTRIBUTES); + options.insert(Options::ENABLE_YAML_STYLE_METADATA_BLOCKS); + options.insert(Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS); + options.insert(Options::ENABLE_OLD_FOOTNOTES); + options.insert(Options::ENABLE_MATH); + options.insert(Options::ENABLE_GFM); + options.insert(Options::ENABLE_DEFINITION_LIST); + options.insert(Options::ENABLE_SUPERSCRIPT); + options.insert(Options::ENABLE_SUBSCRIPT); + options.insert(Options::ENABLE_WIKILINKS); + + let parser = HTMLParser::new_ext(markdown_input, options); + let mut html_output = String::new(); + html::push_html(&mut html_output, parser); + html_output +} + +//pub fn scrollable() -> Result<(), Error> { +// let skin = make_skin(); +// run_app(skin, /* std::string::String */) +//} + +pub fn remove_md_extension(filename: &str) -> &str { + filename.strip_suffix(".md").unwrap_or(filename) +} + +static MD: &str = r#"# Scrollable Markdown in Termimad + +Use the **↓** and **↑** arrow keys to scroll this page. +Use any other key to quit the application. + +*Now I'll describe this example with more words than necessary, in order to be sure to demonstrate scrolling (and **wrapping**, too, thanks to long sentences).* + +## What's shown + +* an **area** fitting the screen (with a max width of 120, to be prettier) +* a markdown text + * **parsed**, + * **skinned**, + * and **wrapped** to fit the width +* a **scrollable** view in *raw terminal mode* + +## Area + +The area specifies the part of the screen where we'll display our markdown. + + let mut area = Area::full_screen(); + area.pad_for_max_width(120); // we don't want a too wide text column + +*(yes the code block centering in this example is a little too much, it's just here to show what's possible)* + +## Parsed Markdown + +The text is parsed from a string. In this example we directly wrap it for the width of the area: + + let text = skin.area_wrapped_text(markdown, &area); + +If we wanted to modify the parsed representation, or modify the area width, we could also have kept the parsed text (*but parsing is cheap*). + +## The TextView + +It's just a text put in an area, tracking your **scroll** position (and whether you want the scrollbar to be displayed). + + let mut text_view = TextView::from(&area, &text); + +## Really Scrolling + +Not two applications handle events in the same way. **Termimad** doesn't try to handle this but lets you write it yourself, which is fairly easily done with **Crossterm** for example: + +``` +let mut events = TerminalInput::new().read_sync(); +loop { + text_view.write()?; + if let Some(Keyboard(key)) = events.next() { + match key { + Up => text_view.try_scroll_lines(-1), + Down => text_view.try_scroll_lines(1), + PageUp => text_view.try_scroll_pages(-1), + PageDown => text_view.try_scroll_pages(1), + _ => break, + } + } +} +``` + +## Skin + +We want *shiny **colors*** (and unreasonnable centering): + + let mut skin = MadSkin::default(); + skin.set_headers_fg(rgb(255, 187, 0)); + skin.bold.set_fg(Yellow); + skin.italic.set_fgbg(Magenta, rgb(30, 30, 40)); + skin.scrollbar.track.set_fg(Rgb{r:30, g:30, b:40}); + skin.scrollbar.thumb.set_fg(Rgb{r:67, g:51, b:0}); + skin.code_block.align = Alignment::Center; + +The scrollbar's colors were also adjusted to be consistent. + +## Usage + +* **↓** and **↑** arrow keys : scroll this page +* any other key : quit + +## And let's just finish by a table + +It's a little out of context but it shows how a wide table can be wrapped in a thin terminal. + +|feature|supported|details| +|-|:-:|- +| tables | yes | pipe based only, alignement not yet supported +| italic, bold | yes | star based only| +| inline code | yes | +| code bloc | yes |with tabs. Fences not supported +| crossed text | ~~not yet~~ | wait... now it works! +| phpbb like links | no | (because it's preferable to show an URL in a terminal) + +(resize your terminal if it's too wide for wrapping to occur) + +"#; diff --git a/src/main.rs b/src/main.rs index 3c13d89..688392c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,22 @@ use axum::response::Redirect; - +use nips::extract; +use nips::extract_html; +use nips::markdown_to_html; +use nips::path::canonicalize_path; +use nips::run_app; +use nips::*; +use nips::{Args, Template}; //use tower_http::services::Redirect; - use axum::{ - extract::Request, /*handler::HandlerWithoutStateExt, http::StatusCode, */routing::get, Router, + extract::Request, /*handler::HandlerWithoutStateExt, http::StatusCode, */ routing::get, + Router, }; use clap::Parser; use pulldown_cmark::Options; use pulldown_cmark::{html, Parser as HTMLParser}; -use rust_embed::Embed; +use std::env; //use rust_embed::RustEmbed; use sha2::{Digest, Sha256}; -use std::env; -use std::fs; use std::fs::File; use std::io; use std::io::{stdout, Write}; @@ -29,277 +33,93 @@ use termimad::crossterm::{ terminal::{self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen}, }; use termimad::*; +use tokio::fs; use tower::ServiceExt; use tower_http::{ services::{ServeDir, ServeFile}, trace::TraceLayer, }; use tracing_subscriber::{fmt, layer::SubscriberExt, EnvFilter, Registry}; - -#[derive(Embed)] -#[folder = "."] -#[include = "*.md"] -#[exclude = "*.DS_Store"] -#[exclude = "target/*"] -#[exclude = "src"] -#[exclude = "src/*"] -#[exclude = ".git"] -#[exclude = ".git/*"] -#[exclude = ".github/*"] -#[exclude = ".gitignore"] -#[exclude = ".justfile"] -#[exclude = ".nojekyll"] -#[exclude = "build.rs"] -#[exclude = "dist-workspace.toml"] -#[exclude = "error.log"] -#[exclude = "output.log"] -#[exclude = "post-commit-history"] -#[exclude = "script.sh"] -#[exclude = "template/Makefile"] -#[exclude = "template/default_config.conf"] -#[exclude = "template/install_script.sh"] -#[exclude = "test_files/tabbed.txtbuild.rs"] -#[exclude = "Cargo.lock"] -#[exclude = "Cargo.toml"] -#[exclude = "LICENSE"] -#[exclude = "Makefile"] -struct Template; - -/// a simple nostr-protocol/nips server -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -struct Args { - /// Enable debug logging. - #[clap(short, long)] - debug: bool, - - /// List all embedded files. - #[clap(short, long)] - list_embedded: bool, - - /// Show the contents of an embedded file using a pager. - //#[clap(short, long, value_name = "NIP", default_value = "README.md")] - #[clap(short, long, value_name = "NIP")] - show: Option, - - /// Axum Serve. - //#[clap(long, default_value = "false")] - #[clap(long, default_value = "false")] - serve: bool, - - /// Sets the port number to listen on - #[arg(short, long, value_parser = clap::value_parser!(u16), default_value_t = 8080)] - port: u16, - - /// Export all embedded files to the current directory. - #[clap(short, long)] - export: bool, - - /// Export all embedded files to the current directory. - #[clap(long)] - export_html: bool, - - /// Export all embedded files to the specified path. - #[clap(long, value_name = "PATH")] - export_path: Option, -} - -fn _make_executable(script_path: &Path) -> io::Result<()> { - let mut permissions = fs::metadata(script_path)?.permissions(); - permissions.set_mode(permissions.mode() | 0o111); - fs::set_permissions(script_path, permissions)?; - tracing::debug!("Made '{}' executable.", script_path.display()); - Ok(()) -} - -fn _execute_script(script_path: &Path) -> io::Result<()> { - tracing::debug!("Executing script: {}", script_path.display()); - let log_file = File::create("output.log")?; - let error_file = File::create("error.log")?; - let mut command = Command::new(script_path); - command - .stdout(Stdio::from(log_file)) - .stderr(Stdio::from(error_file)); - let status = command.spawn()?.wait()?; - if status.success() { - tracing::debug!("Script '{}' executed successfully.", script_path.display()); - Ok(()) - } else { - eprintln!( - "Script '{}' failed with exit code: {:?}", - script_path.display(), - status.code() - ); - Err(io::Error::new( - io::ErrorKind::Other, - format!( - "Script execution failed with exit code: {:?}", - status.code() - ), - )) - } -} - -fn canonicalize_path(path: &Path) -> io::Result { - let absolute_path = if path.is_relative() { - let current_dir = env::current_dir()?; - current_dir.join(path) - } else { - path.to_path_buf() - }; - fs::canonicalize(absolute_path) -} - -fn extract(filename: &str, output_dir: &Path) -> io::Result<()> { - match Template::get(filename) { - Some(embedded_file) => { - let output_path = output_dir.join(filename); - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent)?; - } - let mut outfile = File::create(&output_path)?; - outfile.write_all(embedded_file.data.as_ref())?; - tracing::debug!( - "Successfully exported '{}' to '{}'", - filename, - output_path.display() +//fn _make_executable(script_path: &Path) -> io::Result<()> { +// let mut permissions = fs::metadata(script_path)?.permissions(); +// permissions.set_mode(permissions.mode() | 0o111); +// fs::set_permissions(script_path, permissions)?; +// tracing::debug!("Made '{}' executable.", script_path.display()); +// Ok(()) +//} +// +//fn _execute_script(script_path: &Path) -> io::Result<()> { +// tracing::debug!("Executing script: {}", script_path.display()); +// let log_file = File::create("output.log")?; +// let error_file = File::create("error.log")?; +// let mut command = Command::new(script_path); +// command +// .stdout(Stdio::from(log_file)) +// .stderr(Stdio::from(error_file)); +// let status = command.spawn()?.wait()?; +// if status.success() { +// tracing::debug!("Script '{}' executed successfully.", script_path.display()); +// Ok(()) +// } else { +// eprintln!( +// "Script '{}' failed with exit code: {:?}", +// script_path.display(), +// status.code() +// ); +// Err(io::Error::new( +// io::ErrorKind::Other, +// format!( +// "Script execution failed with exit code: {:?}", +// status.code() +// ), +// )) +// } +//} + +//async fn print_list() -> Result<(), Box> { +async fn print_list() -> i32 { + let args = Args::parse(); + tracing::trace!("Embedded files:"); + for file in Template::iter() { + let _level_filter = if args.debug { + let this_file = Template::get(&file).unwrap(); + //match output of shasum -a 256 + //$ shasum -a 256 README.md + //b238c63f0e937a2b3a3982ecd8328ee03be26584d10723575802e9c6f098f361 README.md + println!( + "{} {}", + calculate_sha256(this_file.data.as_ref()), + file.as_ref() ); - Ok(()) - } - None => Err(io::Error::new( - io::ErrorKind::NotFound, - format!("Embedded file '{}' not found!", filename), - )), + } else { + println!("{}", file.as_ref()); + }; } + 0 as i32 } - -fn remove_md_extension(filename: &str) -> &str { - filename.strip_suffix(".md").unwrap_or(filename) -} - -fn extract_html(filename: &str, output_dir: &Path) -> io::Result<()> { - match Template::get(filename) { +async fn usage() -> i32 { + match Template::get(&"USAGE.md") { Some(embedded_file) => { - let output_path = output_dir - .join("docs") - .join(remove_md_extension(filename).to_owned() + ".html"); - if let Some(parent) = output_path.parent() { - fs::create_dir_all(parent)?; - } - let mut outfile = File::create(&output_path)?; - // let embedded_file_data: &'static [u8] = embedded_file.data.as_ref(); - let embedded_file_data: Vec = embedded_file.data.as_ref().to_vec(); // Create an owned Vec - - //std::str::from_utf8(embedded_file_data) - //outfile.write_all(markdown_to_html(&std::str::from_utf8(embedded_file_data).expect("")).as_bytes())?; - outfile.write_all( - markdown_to_html(&std::str::from_utf8(&embedded_file_data).expect("")).as_bytes(), - )?; - - //outfile.write_all(markdown_to_html(embedded_file_data[0..5])); - tracing::debug!( - "Successfully exported '{}' to '{}'", - filename, - output_path.display() - ); - Ok(()) + let content = String::from_utf8_lossy(embedded_file.data.as_ref()); + let res = markdown_to_html(&content); + tracing::debug!("{}", res); + //print!("{}", res); + //std::process::exit(0); + //#[allow(unreachable_code)] + let skin = make_skin(); + let _res = run_app(skin, (&content).to_string()).await; } - None => Err(io::Error::new( - io::ErrorKind::NotFound, - format!("Embedded file '{}' not found!", filename), - )), - } -} - -fn view_area() -> Area { - let mut area = Area::full_screen(); - area.pad_for_max_width(120); - area -} - -#[allow(unused_variables)] -fn run_app(skin: MadSkin, nip: String) -> Result<(), Error> { - let res = markdown_to_html(&nip); - tracing::debug!("{}", res); - print!("{}", res); - //std::process::exit(0); - //#[allow(unreachable_code)] - let mut w = stdout(); - queue!(w, EnterAlternateScreen)?; - terminal::enable_raw_mode()?; - queue!(w, Hide)?; - let mut view = MadView::from(nip.to_owned(), view_area(), skin); - loop { - view.write_on(&mut w)?; - w.flush()?; - match event::read() { - Ok(Event::Key(KeyEvent { code, .. })) => match code { - Up => view.try_scroll_lines(-1), - Down => view.try_scroll_lines(1), - PageUp => view.try_scroll_pages(-1), - PageDown => view.try_scroll_pages(1), - Char('q') | Esc => break, - _ => {} - }, - Ok(Event::Resize(..)) => { - queue!(w, Clear(ClearType::All))?; - view.resize(&view_area()); - } - _ => {} + None => { + tracing::trace!("Error: {}' not found!", &"USAGE.md"); + print_list().await; } } - terminal::disable_raw_mode()?; - queue!(w, Show)?; - queue!(w, LeaveAlternateScreen)?; - w.flush()?; - Ok(()) -} - -fn make_skin() -> MadSkin { - let mut skin = MadSkin::default(); - skin.table.align = Alignment::Center; - skin.set_headers_fg(AnsiValue(178)); - skin.bold.set_fg(Yellow); - skin.italic.set_fg(Magenta); - skin.scrollbar.thumb.set_fg(AnsiValue(178)); - skin.code_block.align = Alignment::Center; - skin -} - -fn markdown_to_html(markdown_input: &str) -> String { - let mut options = Options::empty(); - //options.insert(Options::all()); - options.insert(Options::ENABLE_TABLES); - options.insert(Options::ENABLE_FOOTNOTES); - options.insert(Options::ENABLE_STRIKETHROUGH); - options.insert(Options::ENABLE_TASKLISTS); - //options.insert(Options::ENABLE_SMART_PUNCTUATION); - options.insert(Options::ENABLE_HEADING_ATTRIBUTES); - options.insert(Options::ENABLE_YAML_STYLE_METADATA_BLOCKS); - options.insert(Options::ENABLE_PLUSES_DELIMITED_METADATA_BLOCKS); - options.insert(Options::ENABLE_OLD_FOOTNOTES); - options.insert(Options::ENABLE_MATH); - options.insert(Options::ENABLE_GFM); - options.insert(Options::ENABLE_DEFINITION_LIST); - options.insert(Options::ENABLE_SUPERSCRIPT); - options.insert(Options::ENABLE_SUBSCRIPT); - options.insert(Options::ENABLE_WIKILINKS); - - let parser = HTMLParser::new_ext(markdown_input, options); - let mut html_output = String::new(); - html::push_html(&mut html_output, parser); - html_output -} - -fn calculate_sha256(data: &[u8]) -> String { - let mut hasher = Sha256::new(); - hasher.update(data); - format!("{:x}", hasher.finalize()) + 0 as i32 } #[tokio::main] async fn main() -> Result<(), Box> { - let args = Args::parse(); + let mut args = Args::parse(); let level_filter = if args.debug { EnvFilter::new("debug") @@ -313,36 +133,34 @@ async fn main() -> Result<(), Box> { tracing::subscriber::set_global_default(subscriber) .expect("Failed to set global default subscriber"); - tracing::debug!("Parsed arguments: {:?}", args); + tracing::trace!("Parsed arguments: {:?}", args); + + if args.usage { + std::process::exit(usage().await) + } else { + 0 + }; + //both cases we assume the server may already + //be running + //nips -s --serve + if args.show.is_none() && args.serve.clone() { + args.serve = false; + //std::process::exit(print_list().await); + std::process::exit(usage().await); + } else if args.show.is_none() { + args.serve = false; + } if args.list_embedded && !args.serve { - tracing::debug!("Embedded files:"); - for file in Template::iter() { - let _level_filter = if args.debug { - let this_file = Template::get(&file).unwrap(); - //match output of shasum -a 256 - //$ shasum -a 256 README.md - //b238c63f0e937a2b3a3982ecd8328ee03be26584d10723575802e9c6f098f361 README.md - println!( - "{} {}", - calculate_sha256(this_file.data.as_ref()), - file.as_ref() - ); - } else { - println!("{}", file.as_ref()); - }; - } - if !args.serve && args.list_embedded { - return Ok(()); - } + let _ = print_list().await; } if args.export && !args.serve { - tracing::info!("Exporting all embedded files to the current directory..."); + tracing::trace!("Exporting all embedded files to the current directory..."); let current_dir = env::current_dir()?; let mut export_count = 0; for file in Template::iter() { - match extract(file.as_ref(), ¤t_dir) { + match extract(file.as_ref(), ¤t_dir).await { Ok(_) => { export_count += 1; } @@ -351,17 +169,17 @@ async fn main() -> Result<(), Box> { } } } - tracing::info!("Successfully exported {} embedded files.", export_count); + tracing::trace!("Successfully exported {} embedded files.", export_count); if !args.serve && args.export { return Ok(()); } } //if args.export_html && !args.serve { - tracing::info!("Exporting all embedded files to the current directory..."); + tracing::trace!("Exporting all embedded files to the current directory..."); let current_dir = env::current_dir()?; let mut export_count = 0; for file in Template::iter() { - match extract_html(file.as_ref(), ¤t_dir) { + match extract_html(file.as_ref(), ¤t_dir).await { Ok(_) => { export_count += 1; } @@ -370,19 +188,19 @@ async fn main() -> Result<(), Box> { } } } - tracing::info!("Successfully exported {} embedded files.", export_count); + tracing::trace!("Successfully exported {} embedded files.", export_count); if !args.serve && args.export_html { return Ok(()); } //} if let Some(export_path) = &args.export_path { - tracing::info!( + tracing::trace!( "Exporting all embedded files to '{}'...", export_path.display() ); let mut export_count = 0; for file in Template::iter() { - match extract(file.as_ref(), export_path) { + match extract(file.as_ref(), export_path).await { Ok(_) => { export_count += 1; } @@ -391,7 +209,7 @@ async fn main() -> Result<(), Box> { } } } - tracing::info!( + tracing::trace!( "Successfully exported {} embedded files to '{}'.", export_count, export_path.display() @@ -413,29 +231,29 @@ async fn main() -> Result<(), Box> { let content = String::from_utf8_lossy(embedded_file.data.as_ref()); let res = markdown_to_html(&content); tracing::debug!("{}", res); - print!("{}", res); - std::process::exit(0); - #[allow(unreachable_code)] + //print!("{}", res); + //std::process::exit(0); + //#[allow(unreachable_code)] let skin = make_skin(); - let _res = run_app(skin, (&content).to_string()); + let _res = run_app(skin, (&content).to_string()).await; if !args.serve && args.show.is_some() { return Ok(()); } } None => { - eprintln!("Error: Embedded NIP file '{}' not found!", filename); - std::process::exit(1); + tracing::trace!("Error: Embedded NIP file '{}' not found!", filename); + print_list().await; } } } - tracing::debug!( + tracing::trace!( "Canonical path of '.': {}", - canonicalize_path(Path::new("."))?.display() + canonicalize_path(Path::new(".")).await?.display() ); - tracing::debug!( + tracing::trace!( "Canonical path of 'docs': {}", - canonicalize_path(Path::new("docs"))?.display() + canonicalize_path(Path::new("docs")).await?.display() ); #[cfg(windows)] @@ -443,14 +261,17 @@ async fn main() -> Result<(), Box> { #[cfg(not(windows))] let absolute_path_str = "/bin/ls"; - tracing::debug!( + tracing::trace!( "Canonical path of '{}': {}", absolute_path_str, - canonicalize_path(Path::new(absolute_path_str))?.display() + canonicalize_path(Path::new(absolute_path_str)) + .await? + .display() ); if args.serve { //MUST be true + tokio::join!(run_app(make_skin(), String::from("01"))); tokio::join!(serve(using_serve_dir_with_assets_fallback(), args.port),); } diff --git a/src/path.rs b/src/path.rs new file mode 100644 index 0000000..ac21457 --- /dev/null +++ b/src/path.rs @@ -0,0 +1,14 @@ +use std::env; +use std::fs; +use std::io; +use std::path::Path; +use std::path::PathBuf; +pub async fn canonicalize_path(path: &Path) -> io::Result { + let absolute_path = if path.is_relative() { + let current_dir = env::current_dir()?; + current_dir.join(path) + } else { + path.to_path_buf() + }; + fs::canonicalize(absolute_path) +}
    yakihonne.com iris.to jumble.social