Skip to content

Commit

Permalink
Add mdsvex and Survex data (#7194)
Browse files Browse the repository at this point in the history
* feat: add mdsvex

Adds support for the [`mdsvex`](https://github.com/pngwn/MDsveX)
language, a Markdown preprocessor for Svelte. At the time of writing,
a search returns 4.1k `mdsvex` files hosted on GitHub:
https://github.com/search?q=path%3A*.svx+NOT+%2F%5CA%28%3B%7C%5C*begin%5Cs%7C%5C*units%5Cs%29%2F+NOT+is%3Afork&type=code

No grammar or LSP exists yet. Progress for these is tracked in
pngwn/MDsveX#121
For code editors, files can best be marked as Markdown.

Sample is taken from [`sveltesociety.dev`](https://github.com/svelte-society/sveltesociety.dev/blob/d2f08cc604a205e51aa8cae3856aae64498096d2/src/routes/recipes/stores/%2Bpage.svx), licenced under the [MIT License](https://github.com/svelte-society/sveltesociety.dev/blob/d2f08cc604a205e51aa8cae3856aae64498096d2/LICENSE).

Colour was chosen to be identical to the [primary accent colour of the `mdsvex` website](https://github.com/pngwn/MDsveX/blob/f29f31c281c93a1f5e604ccc9f8e4cc14208a8d8/packages/site/static/global.css#L21)

Note that the `.svx` extension is also used for [Survex data files](https://survex.com/docs/manual/datafile.htm).
Since that is currently not supported, no heuristic was added.
Should the need to add such a heuristic arise in the future,
the following RegEx will match for Survex data files:
`/\A(;|\*begin\s|\*units\s)/`

* feat: add Survex data

Adds support for the [Survex data](https://survex.com/docs/manual/datafile.htm) format, a plaintext data file format used for mapping caves. At the time of writing, a search returns 2.4k Survex data files hosted on GitHub: https://github.com/search?type=code&q=NOT+is%3Afork+path%3A*.svx+%2F%5CA%28%3B%7C%5C*%5B%5E*%5D%2B%24%29%2F

No grammar or LSP exists for these, to my knowledge.

Sample is taken from the [Philippine Surveys from the BEC expedition 2015](https://github.com/speleo/philippine-surveys-2015/blob/bad0f4490661908bcda3054226ab178bfa799f4a/Romeo/thTMPDIR/data.svx), in particular a temporary data file created by Therion during the mapping of the Romeo cave, licenced under the [Creative Commons Zero v1.0 Universal](https://github.com/speleo/philippine-surveys-2015/blob/bad0f4490661908bcda3054226ab178bfa799f4a/LICENSE) licence.

Colour was chosen as the [background colour of the navigation bar of the Survex website](https://survex.com).

* feat: add heuristic to distinguish Survex data and `mdsvex` files

* test: add `.svx` heuristics test
  • Loading branch information
mahtaran authored Jan 8, 2025
1 parent 63cfd70 commit d2707a8
Show file tree
Hide file tree
Showing 5 changed files with 376 additions and 0 deletions.
5 changes: 5 additions & 0 deletions lib/linguist/heuristics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,11 @@ disambiguations:
rules:
- language: STL
pattern: '\A\s*solid(?:$|\s)[\s\S]*^endsolid(?:$|\s)'
- extensions: ['.svx']
rules:
- language: Survex data
pattern: '\A(;|\*[^*]+$)'
- language: mdsvex
- extensions: ['.sw']
rules:
- language: Sway
Expand Down
19 changes: 19 additions & 0 deletions lib/linguist/languages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7121,6 +7121,14 @@ SuperCollider:
tm_scope: source.supercollider
ace_mode: text
language_id: 361
Survex data:
type: data
color: "#ffcc99"
tm_scope: none
ace_mode: text
extensions:
- ".svx"
language_id: 24470517
Svelte:
type: markup
color: "#ff3e00"
Expand Down Expand Up @@ -8578,6 +8586,17 @@ mcfunction:
tm_scope: source.mcfunction
ace_mode: text
language_id: 462488745
mdsvex:
type: markup
color: "#5f9ea0"
tm_scope: none
ace_mode: markdown
codemirror_mode: gfm
codemirror_mime_type: text/x-gfm
wrap: true
extensions:
- ".svx"
language_id: 566198445
mupad:
type: programming
color: "#244963"
Expand Down
112 changes: 112 additions & 0 deletions samples/Survex data/data.svx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
*units declination clino compass degrees
*units tape depth counter northing easting altitude metres
*infer plumbs off
*infer equates off
*data normal from to tape compass clino
1 2 0.000000 0.000000 0.000000
1 3 4.703000 26.220000 -76.540000
1 4 1.690000 323.820000 -3.600000
1 5 1.422000 312.960000 -2.090000
1 6 8.435000 29.250000 -49.010000
6 7 7.731000 164.250000 86.930000
6 8 5.100000 128.360000 -2.190000
6 9 3.319000 296.720000 1.420000
6 10 1.614000 74.670000 -82.320000
6 11 9.758000 38.810000 -0.940000
11 12 2.609000 299.550000 -2.620000
11 13 4.823000 338.820000 81.730000
11 14 1.466000 201.680000 -81.210000
11 15 0.221000 110.670000 0.000000
11 16 7.906000 12.690000 1.590000
16 17 1.944000 103.060000 -8.250000
16 18 3.644000 43.730000 81.140000
16 19 1.495000 177.730000 -86.300000
16 20 7.654000 17.900000 1.480000
20 21 2.808000 129.860000 88.310000
20 22 1.411000 177.740000 -85.210000
20 23 0.794000 115.710000 -2.030000
20 24 1.178000 308.890000 10.490000
20 25 3.228000 40.110000 38.820000
25 26 8.423000 10.730000 -20.330000
26 27 2.162000 118.280000 1.140000
26 28 1.366000 163.430000 -87.340000
26 29 3.580000 244.260000 88.990000
26 30 8.552000 30.520000 -3.350000
30 31 4.492000 359.410000 82.530000
30 32 1.412000 159.350000 -82.920000
30 33 2.375000 290.110000 10.050000
30 34 6.983000 357.250000 -16.650000
34 35 2.221000 109.650000 -4.600000
34 36 6.166000 111.410000 86.590000
34 37 1.179000 265.890000 -88.360000
34 38 7.070000 30.230000 4.030000
38 39 2.453000 114.610000 -5.090000
38 40 1.917000 318.470000 -86.140000
38 41 4.632000 83.950000 84.610000
38 42 10.243000 37.240000 3.130000
42 43 5.507000 250.480000 -1.430000
42 44 3.788000 267.750000 -2.800000
42 45 2.792000 310.360000 -6.680000
42 46 2.045000 355.690000 85.410000
42 47 2.101000 210.320000 -86.100000
42 48 0.387000 122.260000 -3.730000
42 49 6.626000 1.590000 -15.130000
49 50 1.708000 78.770000 -5.420000
49 51 2.829000 96.830000 -4.770000
49 52 3.511000 107.240000 -4.240000
49 53 4.122000 137.830000 -4.160000
49 54 3.248000 109.590000 81.240000
49 55 1.643000 295.820000 -84.010000
49 56 6.662000 127.780000 -4.970000
56 57 1.180000 35.540000 -0.610000
56 58 1.598000 92.980000 86.680000
56 59 1.805000 113.370000 -87.500000
56 60 12.567000 117.270000 -62.390000
56 61 7.164000 130.200000 -6.230000
62 56 10.761000 283.930000 55.460000
62 63 4.307000 188.140000 -5.300000
62 64 3.282000 267.710000 -5.500000
62 65 6.639000 156.000000 -45.030000
62 66 5.828000 226.270000 6.650000
62 67 3.827000 322.550000 0.510000
62 68 3.055000 244.020000 -59.220000
62 69 7.334000 1.070000 16.670000
69 70 2.540000 110.530000 -9.660000
69 71 3.478000 72.930000 80.560000
69 72 1.256000 272.750000 -86.180000
69 73 4.947000 36.880000 -64.040000
69 74 7.885000 44.500000 -34.760000
74 75 3.051000 287.390000 -4.730000
74 76 5.834000 210.900000 87.410000
74 77 1.530000 248.300000 -88.730000
74 78 4.298000 203.210000 -5.050000
74 79 4.487000 219.230000 -6.700000
74 80 4.980000 232.310000 -5.230000
74 81 0.518000 116.810000 -9.120000
74 82 3.915000 30.720000 -11.670000
74 83 8.740000 359.490000 -8.390000
83 84 2.475000 125.590000 -6.230000
83 85 5.845000 58.680000 78.990000
83 86 1.527000 141.000000 -85.170000
83 87 4.069000 64.190000 -34.720000
87 88 1.623000 300.490000 -1.810000
87 89 1.425000 14.040000 -85.630000
87 90 6.143000 305.610000 79.830000
87 91 1.016000 120.890000 20.990000
87 92 7.527000 10.210000 -63.670000
62 93 0.000000 0.000000 0.000000
62 94 4.554000 154.150000 -34.040000
94 95 0.945000 291.290000 -3.130000
94 96 2.456000 51.160000 -20.170000
94 97 0.630000 257.380000 84.730000
94 98 0.670000 223.450000 80.150000
94 99 1.727000 340.260000 -80.060000
94 100 5.100000 185.130000 -72.020000
100 101 0.818000 267.270000 -2.770000
100 102 0.515000 85.580000 18.070000
100 103 1.416000 237.600000 83.650000
100 104 1.090000 257.980000 -80.550000
100 105 2.065000 221.760000 5.370000
100 106 0.710000 263.550000 -14.410000
106 107 2.861000 358.860000 -73.700000
106 108 3.496000 7.230000 -48.650000
233 changes: 233 additions & 0 deletions samples/mdsvex/Stores.svx
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
---
title: Stores
layout: recipeCategory
icon: database
published: 2021-06-14T11:30:41-07:00
updated: 2023-01-27T20:05:46-08:00
---

<script>import Warning from '$lib/components/recipes/Warning.svelte'</script>

## This needs to be re-organised into separate recipes

<Warning>
This guide assumes you understand the basics of Svelte Stores. If you aren't familiar with them
then working through the{' '}
<a href="http://www.svelte.dev/tutorial/writable-stores" target="_blank">
relevant tutorial
</a>{' '}
and reading the{' '}
<a href="http://www.svelte.dev/docs#svelte_store" target="_blank">
store documentation
</a>{' '}
are highly recommended.
</Warning>

Svelte stores offer a simple mechanism to handle shared state in your Svelte application but looking beyond the built-in store implementations will unlock a whole world of power that you could never have dreamed of. In this episode of _The Tinest Kitchen_ we'll take a close look at [The Store Contract](#the-store-contract), learn how to implement [Custom Stores](#custom-stores), by making use of the built-in store API, and explore how we can implement [a completely custom store]() without using the built-in stores at all.

### The store contract

The built-in Svelte stores (`readable`, `writable`, and `derived`) are just store _implementations_ and while they are perfectly capable of handling many tasks, sometimes you need something more specific. Although often overlooked, the store _contract_ is what gives these stores their power and flexibility. Without this contract, svelte stores would be awkward to use and require significant amounts of boilerplate.

Svelte does not compile your javascript files and, as such, only observes the store contract inside Svelte components.

#### `store.subscribe`

At its simplest, the store contract is this: any time Svelte sees a variable prepended with `$` in a Svelte component (such as `$store`) it calls the `subscribe` method of that variable. The `subscribe` method must take a single argument, which is a function, and it must _return_ a function that allows any subscribers to unsubscribe when necessary. Whenever the callback function is called, it must be passed the current store value as an argument. The callback passed to subscribe should be called when subscribing and anytime the store value changes.

The following examples aren't the _exact_ code that Svelte produces, rather, simplified examples to illustrate the behaviour.

This:

```js
import { my_store } from './store.js';

console.log($my_store);
```

Becomes something like this:

```js
import { my_store } from './store.js';

let $my_store;
const unsubscribe = my_store.subscribe((value) => ($my_store = value));
onDestroy(unsubscribe);

console.log($my_store);
```

The callback function passed to `my_store.subscribe` is called immediately and whenever the store value changes. Here, Svelte has automatically produced some code to assign the `my_store` value to `$my_store` whenever it is called. If `$my_store` is referenced in the component, it also causes those parts of the component to update when the store value changes. When the component is destroyed, Svelte calls the unsubscribe function returned from `my_store.subscribe`.

#### `store.set`

Optionally, a store can have a `set` method. Whenever there is an assignment to a variable prepended with `$` in a Svelte component it calls the `set` method of that variable with newly mutated or reassigned `$variable` as an argument. Typically, this `set` argument should update the store value and call all subscribers, but this is not required. For example, Svelte's `tweened` and `spring` stores do not immediately update their values but rather schedule updates on every frame for as long as the animation lasts. If you decide to take this approach with `set`, we advise not [binding](https://svelte.dev/tutorial/store-bindings) to these stores as the behaviour could be unpredictable.

This:

```js
$my_store = 'Hello';
```

Will become something like:

```js
$my_store = 'Hello';
my_store.set($my_store);
```

The same is true when assigning to a nested property of a store.

This:

```js
$my_store.greeting = 'Hello';
```

Becomes:

```js
$my_store.greeting = 'Hello';
my_store.set($my_store);
```

Although Svelte's built-in stores also have an `update` method, this is not part of the contract and is not required to benefit from the automatic subscriptions, unsubscriptions, and updates that the store contract provides. Stores can have as many additional methods as you like, allowing you to build powerful abstractions that take advantage of the automatic reactivity and cleanup that the store contract provides.

To summarise, the store contract states that svelte stores must be an object containing the following methods:

- `subscribe` - Automatically called whenever svelte sees a `$` prepended variable (like `$store`) ensuring that the `$` prepended value always has the current store value. Subscribe must accept a function which is called both immediately, and whenever the store value changes, it must return an unsubscribe function. The callback function must be passed the current store value as an argument whenever it is called.
- `set` - Automatically called whenever Svelte sees an assignment to a `$` prepended variable (like `$store = 'value'`). This should generally update the store value and call all subscribers.

### Custom stores

Now we know what Svelte needs to make use of the shorthand store syntax, we can get to work implementing a custom store by augmenting a svelte store and re-exporting it. Since Svelte doesn't care about additional methods being present on store objects, we are free to add whatever we like as long as `subscribe`, and optionally `set`, are present.

#### Linked stores

In this first example, we are creating a function that returns two linked stores that update when their partner changes, this example uses this linked store to convert temperatures from Celsius to Fahrenheit and vice-versa. The interface looks like this:

```js
store : { subscribe, set }
function(a_to_b_function, b_to_a_function): [store, store]
```

To implement this store, we need to create two writable stores, write custom `set` methods for each, and return an array of store objects containing this `set` method.

We define a function first as this implementation is a store _creator_ allowing us plenty of flexibility. The function needs to take two parameters, each being a callback function which is called when the stores are updated. The first function takes the first store value and returns a value that sets the value of the second store. The second argument does the opposite. One of these functions is called when the relevant `set` method is called.

```js
import { writable } from 'svelte/store';

function synced(a_to_b, b_to_a) {
const a = writable();
const b = writable();
}
```

The `set` methods call their own `set` with the provided value and call the partner store's `set` when the provided value is passed through the callback function.

```js
// called when store_a.set is called or its binding reruns
function a_set($a) {
a.set($a);
b.set(a_to_b($a));
}

// called when store_b.set is called or its binding reruns
function b_set($b) {
a.set(b_to_a($b));
b.set($b);
}
```

All we need to do now is return an array of objects each containing the correct `subscribe` and `set` method:

```js
return [
{ subscribe: a.subscribe, set: a_set },
{ subscribe: b.subscribe, set: b_set }
];
```

Inside a component, we can use this synced store creator by deconstructing the returned array. This ensures Svelte can subscribe to each store individually, as stores definitions need to be at the top level for this to happen. This store can be imported and reused in any component.

```js
import { synced } from './synced.js';

const [a, a_plus_five] = synced(
(a) => a + 5,
(b) => a - 5
);

$a = 0; // set an initial value
```

Since we have written custom `set` methods, we are also free to bind to each individual store. When one store updates, the other also updates after the provided function is applied to the value.

See it in action below. The following example uses the `synced` store to convert between Celsius and Fahrenheit in both directions.

```html
<script>
import { synced } from './linkable';

export let initialCelsius = null;
export let initialFahrenheit = null;

const [C, F] = synced(
(C) => (C * 9) / 5 + 32,
(F) => ((F - 32) * 5) / 9
);

if (initialCelsius && initialFahrenheit) {
console.error(
'You can only set one initial temperature. Please set initialCelsius or initialFahrenheit but not both.'
);
} else if (initialCelsius) {
$C = initialCelsius;
} else if (initialFahrenheit) {
$F = initialFahrenheit;
} else {
$C = 0;
}
</script>

<input bind:value="{$C}" type="number" /> ºC = <input bind:value="{$F}" type="number" /> ºF
```

Play around with it in the [REPL](https://svelte.dev/repl/abbc56bdbd6e45c8ad5cd6f75108c6d8?version=3).

### a custom implementation of the builtin store

A simple store is about 20 lines of code, in many cases the built-in stores provide good primitives you can build on but sometimes it makes sense to write your own.

The most basic implementation would look something like this ([REPL](https://svelte.dev/repl/1c055b975b6d42f5b8623bad5d92e8fc?version=3.14.0)) (this is simpler than the built-in stores):

```js
function writable(init) {
let _val = init;
const subs = [];

const subscribe = (cb) => {
subs.push(cb);
cb(_val);

return () => {
const index = subs.findIndex((fn) => fn === cb);
subs.splice(index, 1);
};
};

const set = (v) => {
_val = v;
subs.forEach((fn) => fn(_val));
};

const update = (fn) => set(fn(_val));

return { subscribe, set, update };
}
```

From this point you could add whatever functionality you wanted.

Edit: Probably worth mentioning that this is a full writable implementation, only the subscribe method and its return value (an unsubscribe function) are required to be a valid store.
Loading

0 comments on commit d2707a8

Please sign in to comment.