feat(lang): complete extends to modifies keyword migration
This commit completes the migration started in the previous commit, updating all remaining files: - Lexer: Changed token from Extends to Modifies - Parser: Updated lalrpop grammar rules and AST field names - AST: Renamed Schedule.extends field to modifies - Grammar: Updated tree-sitter grammar.js - Tree-sitter: Regenerated parser.c and node-types.json - Examples: Updated baker-family work schedules - Tests: Updated schedule composition tests and corpus - Docs: Updated all reference documentation and tutorials - Validation: Updated error messages and validation logic - Package: Bumped version to 0.3.1 in all package manifests All 554 tests pass.
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
|
||||
This revision addresses Sienna's feedback:
|
||||
|
||||
1. ✅ **Simplified composition** - Keep only `extends` and `override`, remove `remove`/`append`/`replace all`
|
||||
1. ✅ **Simplified composition** - Keep only `modifies` and `override`, remove `remove`/`append`/`replace all`
|
||||
2. ✅ **User-configurable calendars** - Define seasons/days as enums in schema, not hardcoded
|
||||
3. ✅ **Behavior references** - Blocks reference behaviors/actions, not string activity names
|
||||
4. ✅ **Required block names** - All blocks must be named for override system
|
||||
@@ -26,7 +26,7 @@ This revision addresses Sienna's feedback:
|
||||
This document proposes a **year-long, composable schedule system** for the Storybook DSL that:
|
||||
|
||||
- **User-defined calendars**: Seasons, days, months defined as enums (8 seasons? 5-day weeks? You decide!)
|
||||
- **Simple composition**: Only `extends` and `override` (no complex operators)
|
||||
- **Simple composition**: Only `modifies` and `override` (no complex operators)
|
||||
- **Behavior integration**: Schedule blocks reference behaviors, not string activities
|
||||
- **Temporal patterns**: Day-specific, seasonal, and recurring event patterns
|
||||
- **Type-safe**: Validator ensures calendar references are valid
|
||||
@@ -166,7 +166,7 @@ Users can `use schema::standard_calendar::*` or define their own!
|
||||
### 2.1 Only Two Operators
|
||||
|
||||
**Keep:**
|
||||
- `extends` - Inherit from base schedule
|
||||
- `modifies` - Inherit from base schedule
|
||||
- `override` - Replace specific blocks
|
||||
|
||||
**Remove:**
|
||||
@@ -185,7 +185,7 @@ schedule BaseWorkday {
|
||||
block sleep_late { 22:00 - 24:00, action: Sleep }
|
||||
}
|
||||
|
||||
schedule BakerSchedule extends BaseWorkday {
|
||||
schedule BakerSchedule modifies BaseWorkday {
|
||||
override work { 5:00 - 13:00, action: BakeryWork }
|
||||
block baking { 3:00 - 5:00, action: PrepBread } // New block, not override
|
||||
}
|
||||
@@ -207,7 +207,7 @@ schedule Base {
|
||||
block work { 9:00 - 17:00, action: GenericWork }
|
||||
}
|
||||
|
||||
schedule Baker extends Base {
|
||||
schedule Baker modifies Base {
|
||||
override work { 5:00 - 13:00, action: BakeryWork }
|
||||
}
|
||||
// Baker's 'work' block replaces Base's 'work' block entirely
|
||||
@@ -222,7 +222,7 @@ schedule Base {
|
||||
block work { 9:00 - 17:00, action: GenericWork, location: Office }
|
||||
}
|
||||
|
||||
schedule Remote extends Base {
|
||||
schedule Remote modifies Base {
|
||||
override work { location: Home } // Only change location
|
||||
}
|
||||
// Result: work { 9:00 - 17:00, action: GenericWork, location: Home }
|
||||
@@ -322,7 +322,7 @@ schedule WeeklyRoutine {
|
||||
**Can extend base schedule per day:**
|
||||
```storybook
|
||||
schedule WeeklyRoutine {
|
||||
on Fireday extends BaseWorkday {
|
||||
on Fireday modifies BaseWorkday {
|
||||
override work { action: FireWork }
|
||||
block ritual { 6:00 - 7:00, action: FireRitual }
|
||||
}
|
||||
@@ -363,7 +363,7 @@ season (Bloom, Growth) { // Both use same schedule
|
||||
**Named recurring events:**
|
||||
|
||||
```storybook
|
||||
schedule AnnualPattern extends BaseWorkday {
|
||||
schedule AnnualPattern modifies BaseWorkday {
|
||||
recurrence MarketDay on Earthday {
|
||||
block market { 8:00 - 13:00, action: SellAtMarket }
|
||||
}
|
||||
@@ -527,7 +527,7 @@ block work {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Schedule {
|
||||
pub name: String,
|
||||
pub extends: Option<Vec<String>>, // extends BaseSchedule
|
||||
pub modifies: Option<Vec<String>>, // modifies BaseSchedule
|
||||
pub items: Vec<ScheduleItem>, // blocks and patterns
|
||||
pub span: Span,
|
||||
}
|
||||
@@ -567,7 +567,7 @@ pub struct ScheduleOverride {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DayPattern {
|
||||
pub day: String, // Reference to DayOfWeek enum value
|
||||
pub extends: Option<Vec<String>>, // Optional base schedule
|
||||
pub modifies: Option<Vec<String>>, // Optional base schedule
|
||||
pub blocks: Vec<ScheduleBlock>,
|
||||
pub span: Span,
|
||||
}
|
||||
@@ -604,13 +604,13 @@ pub enum RecurrenceSpec {
|
||||
|
||||
```lalrpop
|
||||
pub Schedule: Schedule = {
|
||||
"schedule" <name:Ident> <extends:ExtendsClause?> "{"
|
||||
"schedule" <name:Ident> <modifies:ModifiesClause?> "{"
|
||||
<items:ScheduleItem*>
|
||||
"}" => Schedule { name, extends, items, span }
|
||||
"}" => Schedule { name, modifies, items, span }
|
||||
};
|
||||
|
||||
ExtendsClause: Vec<String> = {
|
||||
"extends" <QualifiedPath>
|
||||
ModifiesClause: Vec<String> = {
|
||||
"modifies" <QualifiedPath>
|
||||
};
|
||||
|
||||
ScheduleItem: ScheduleItem = {
|
||||
@@ -664,8 +664,8 @@ ScheduleOverride: ScheduleOverride = {
|
||||
|
||||
// Day pattern: on Fireday { blocks }
|
||||
DayPattern: DayPattern = {
|
||||
<day:Ident> <extends:ExtendsClause?> "{" <blocks:ScheduleBlock*> "}" =>
|
||||
DayPattern { day, extends, blocks, span }
|
||||
<day:Ident> <modifies:ModifiesClause?> "{" <blocks:ScheduleBlock*> "}" =>
|
||||
DayPattern { day, modifies, blocks, span }
|
||||
};
|
||||
|
||||
// Season pattern: season (Bloom, Growth) { blocks }
|
||||
@@ -797,7 +797,7 @@ schedule BaseWorkday {
|
||||
block sleep_late { 22:00 - 24:00, action: Sleep }
|
||||
}
|
||||
|
||||
schedule BakerSchedule extends BaseWorkday {
|
||||
schedule BakerSchedule modifies BaseWorkday {
|
||||
override work { 5:00 - 13:00, action: BakeryWork }
|
||||
block prep { 3:00 - 5:00, action: PrepBread }
|
||||
}
|
||||
@@ -896,7 +896,7 @@ schedule MageSchedule {
|
||||
### Example 5: Recurring Market Days
|
||||
|
||||
```storybook
|
||||
schedule BakerAnnual extends BaseWorkday {
|
||||
schedule BakerAnnual modifies BaseWorkday {
|
||||
recurrence MarketDay on Earthday {
|
||||
block market { 8:00 - 13:00, action: SellBread }
|
||||
}
|
||||
@@ -921,11 +921,11 @@ schedule BaseHuman {
|
||||
block sleep_late { 22:00 - 24:00, action: Sleep }
|
||||
}
|
||||
|
||||
schedule Worker extends BaseHuman {
|
||||
schedule Worker modifies BaseHuman {
|
||||
block work { 9:00 - 17:00, action: GenericWork }
|
||||
}
|
||||
|
||||
schedule Baker extends Worker {
|
||||
schedule Baker modifies Worker {
|
||||
override work { 5:00 - 13:00, action: BakeryWork }
|
||||
override breakfast { 13:30 - 14:00, action: Eat } // Late breakfast after work
|
||||
block prep { 3:00 - 5:00, action: PrepBread }
|
||||
@@ -996,7 +996,7 @@ SCHEDULES Section:
|
||||
|
||||
Schedule:
|
||||
- name: String
|
||||
- parent_schedule_id: Option<u32> // extends reference
|
||||
- parent_schedule_id: Option<u32> // modifies reference
|
||||
- blocks: [ScheduleBlock...]
|
||||
- patterns: [SchedulePattern...]
|
||||
|
||||
@@ -1032,7 +1032,7 @@ PatternKind:
|
||||
Character: Martha
|
||||
uses schedule: BakerSchedule
|
||||
|
||||
BakerSchedule extends BaseWorkday
|
||||
BakerSchedule modifies BaseWorkday
|
||||
|
||||
Merge result for today (Fireday, EarlySummer):
|
||||
03:00-05:00 → PrepBread
|
||||
@@ -1063,7 +1063,7 @@ Merge result for today (Fireday, EarlySummer):
|
||||
5. Write tests for custom calendars
|
||||
|
||||
### Phase 3: Parser Implementation (Week 1-2)
|
||||
1. Implement simplified `extends`/`override` parsing
|
||||
1. Implement simplified `modifies`/`override` parsing
|
||||
2. Implement `on <Day>` patterns
|
||||
3. Implement `season (<Seasons>)` patterns
|
||||
4. Implement `recurrence <Name> on <Spec>` patterns
|
||||
@@ -1100,7 +1100,7 @@ Merge result for today (Fireday, EarlySummer):
|
||||
|
||||
### Must Have
|
||||
- [x] User-defined calendars via enums
|
||||
- [x] Simplified composition (`extends`, `override` only)
|
||||
- [x] Simplified composition (`modifies`, `override` only)
|
||||
- [x] Behavior references (not string activities)
|
||||
- [x] Required block names
|
||||
- [x] Required time ranges
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document proposes a **year-long, composable schedule system** for the Storybook DSL that extends the current simple daily schedules to support:
|
||||
This document proposes a **year-long, composable schedule system** for the Storybook DSL that modifies the current simple daily schedules to support:
|
||||
|
||||
- **Temporal composition**: Days → Weeks → Months → Seasons → Year
|
||||
- **Template inheritance**: Schedules extend and override like behavior templates
|
||||
@@ -162,7 +162,7 @@ schedule BakeryHours {
|
||||
|
||||
**Yearly Schedule** - Full year with events and overrides:
|
||||
```storybook
|
||||
schedule AnnualPattern extends WorkWeek {
|
||||
schedule AnnualPattern modifies WorkWeek {
|
||||
event Christmas on Dec 25 {
|
||||
block rest { 0:00 - 24:00, activity: rest }
|
||||
}
|
||||
@@ -175,9 +175,9 @@ schedule AnnualPattern extends WorkWeek {
|
||||
|
||||
---
|
||||
|
||||
## 4. Composition System (i just want `override` support, we can yank `remove` and `append` and `remove all`, but `extends` is good)
|
||||
## 4. Composition System (i just want `override` support, we can yank `remove` and `append` and `remove all`, but `modifies` is good)
|
||||
|
||||
### 4.1 Template Inheritance with `extends`
|
||||
### 4.1 Template Inheritance with `modifies`
|
||||
|
||||
**Base Schedule:**
|
||||
```storybook
|
||||
@@ -191,7 +191,7 @@ schedule BaseWorkday {
|
||||
|
||||
**Extended Schedule:**
|
||||
```storybook
|
||||
schedule BakerSchedule extends BaseWorkday {
|
||||
schedule BakerSchedule modifies BaseWorkday {
|
||||
override work { 5:00 - 13:00 } // Earlier hours
|
||||
append baking { 3:00 - 5:00, activity: prepare }
|
||||
}
|
||||
@@ -209,7 +209,7 @@ Block sleep: 22:00 - 24:00 + 0:00 - 6:00 (sleep) ← inherited
|
||||
|
||||
**`override <block> { fields }`** - Replace specific block properties:
|
||||
```storybook
|
||||
schedule BakerSchedule extends BaseWorkday {
|
||||
schedule BakerSchedule modifies BaseWorkday {
|
||||
override work {
|
||||
time: 5:00 - 13:00,
|
||||
location: Bakery
|
||||
@@ -219,14 +219,14 @@ schedule BakerSchedule extends BaseWorkday {
|
||||
|
||||
**`remove <block>`** - Delete inherited block:
|
||||
```storybook
|
||||
schedule RetiredSchedule extends BaseWorkday {
|
||||
schedule RetiredSchedule modifies BaseWorkday {
|
||||
remove work // No more work!
|
||||
}
|
||||
```
|
||||
|
||||
**`append <block> { fields }`** - Add new block:
|
||||
```storybook
|
||||
schedule ExtendedDay extends BaseWorkday {
|
||||
schedule ExtendedDay modifies BaseWorkday {
|
||||
append meditation { 5:00 - 5:30, activity: meditate }
|
||||
}
|
||||
```
|
||||
@@ -246,11 +246,11 @@ schedule Base {
|
||||
block work { 9:00 - 17:00, activity: work }
|
||||
}
|
||||
|
||||
schedule WorkerSchedule extends Base {
|
||||
schedule WorkerSchedule modifies Base {
|
||||
override work { 8:00 - 16:00 }
|
||||
}
|
||||
|
||||
schedule SeniorWorkerSchedule extends WorkerSchedule {
|
||||
schedule SeniorWorkerSchedule modifies WorkerSchedule {
|
||||
override work { 9:00 - 15:00 } // Shorter hours
|
||||
}
|
||||
|
||||
@@ -271,7 +271,7 @@ schedule SeniorWorkerSchedule extends WorkerSchedule {
|
||||
**Simple Weekday/Weekend Split:**
|
||||
```storybook
|
||||
schedule WorkWeek {
|
||||
weekday (mon, tue, wed, thu, fri) extends BaseWorkday {
|
||||
weekday (mon, tue, wed, thu, fri) modifies BaseWorkday {
|
||||
// Monday-Friday use BaseWorkday
|
||||
}
|
||||
|
||||
@@ -285,11 +285,11 @@ schedule WorkWeek {
|
||||
**Day-Specific Customization:**
|
||||
```storybook
|
||||
schedule DetailedWeek {
|
||||
on monday extends WorkdaySchedule {
|
||||
on monday modifies WorkdaySchedule {
|
||||
append meeting { 9:00 - 10:00, activity: meeting }
|
||||
}
|
||||
|
||||
on friday extends WorkdaySchedule {
|
||||
on friday modifies WorkdaySchedule {
|
||||
override work { 9:00 - 15:00 } // Early finish!
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ schedule FarmerSchedule {
|
||||
|
||||
**Holiday Definitions:**
|
||||
```storybook
|
||||
schedule AnnualSchedule extends WorkWeek {
|
||||
schedule AnnualSchedule modifies WorkWeek {
|
||||
event Christmas on Dec 25 {
|
||||
block family { 0:00 - 24:00, activity: family_time }
|
||||
}
|
||||
@@ -362,7 +362,7 @@ schedule AnnualSchedule extends WorkWeek {
|
||||
|
||||
**Recurring Events:** (hmmmmmmmmmm sell me on this one.)
|
||||
```storybook
|
||||
schedule MarketSchedule extends BaseSchedule {
|
||||
schedule MarketSchedule modifies BaseSchedule {
|
||||
every saturday {
|
||||
block market { 8:00 - 13:00, activity: sell_at_market }
|
||||
}
|
||||
@@ -385,7 +385,7 @@ schedule MarketSchedule extends BaseSchedule {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Schedule {
|
||||
pub name: String,
|
||||
pub extends: Option<Vec<String>>, // NEW: extends BaseSchedule
|
||||
pub modifies: Option<Vec<String>>, // NEW: modifies BaseSchedule
|
||||
pub items: Vec<ScheduleItem>, // NEW: blocks, patterns, events
|
||||
pub span: Span,
|
||||
}
|
||||
@@ -423,7 +423,7 @@ pub struct ScheduleOverride {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WeekdayPattern {
|
||||
pub days: Vec<DayOfWeek>, // [mon, tue, wed]
|
||||
pub extends: Option<Vec<String>>, // Optional base schedule
|
||||
pub modifies: Option<Vec<String>>, // Optional base schedule
|
||||
pub blocks: Vec<ScheduleBlock>,
|
||||
pub span: Span,
|
||||
}
|
||||
@@ -431,7 +431,7 @@ pub struct WeekdayPattern {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DayPattern {
|
||||
pub day: DayOfWeek, // monday
|
||||
pub extends: Option<Vec<String>>,
|
||||
pub modifies: Option<Vec<String>>,
|
||||
pub blocks: Vec<ScheduleBlock>,
|
||||
pub span: Span,
|
||||
}
|
||||
@@ -485,13 +485,13 @@ pub enum RecurringPattern {
|
||||
// In parser.lalrpop
|
||||
|
||||
pub Schedule: Schedule = {
|
||||
"schedule" <name:Ident> <extends:ExtendsClause?> "{"
|
||||
"schedule" <name:Ident> <modifies:ModifiesClause?> "{"
|
||||
<items:ScheduleItem*>
|
||||
"}" => Schedule { name, extends, items, span }
|
||||
"}" => Schedule { name, modifies, items, span }
|
||||
};
|
||||
|
||||
ExtendsClause: Vec<String> = {
|
||||
"extends" <QualifiedPath>
|
||||
ModifiesClause: Vec<String> = {
|
||||
"modifies" <QualifiedPath>
|
||||
};
|
||||
|
||||
ScheduleItem: ScheduleItem = {
|
||||
@@ -537,16 +537,16 @@ ReplaceAllStmt = {
|
||||
|
||||
// Weekday pattern: weekday (mon, tue, wed) { blocks }
|
||||
WeekdayPattern: WeekdayPattern = {
|
||||
"weekday" "(" <days:Comma<DayOfWeek>> ")" <extends:ExtendsClause?> "{"
|
||||
"weekday" "(" <days:Comma<DayOfWeek>> ")" <modifies:ModifiesClause?> "{"
|
||||
<blocks:ScheduleBlock*>
|
||||
"}" => WeekdayPattern { days, extends, blocks, span }
|
||||
"}" => WeekdayPattern { days, modifies, blocks, span }
|
||||
};
|
||||
|
||||
// Day pattern: on monday { blocks }
|
||||
DayPattern: DayPattern = {
|
||||
"on" <day:DayOfWeek> <extends:ExtendsClause?> "{"
|
||||
"on" <day:DayOfWeek> <modifies:ModifiesClause?> "{"
|
||||
<blocks:ScheduleBlock*>
|
||||
"}" => DayPattern { day, extends, blocks, span }
|
||||
"}" => DayPattern { day, modifies, blocks, span }
|
||||
};
|
||||
|
||||
// Season pattern: season summer { blocks }
|
||||
@@ -602,7 +602,7 @@ DateSpec: DateSpec = {
|
||||
### 8.1 Schedule Merge Algorithm
|
||||
|
||||
**Same as Behavior Merging:**
|
||||
1. Resolve `extends` reference to base schedule
|
||||
1. Resolve `modifies` reference to base schedule
|
||||
2. Collect base schedule's blocks
|
||||
3. Apply overrides/removes/appends from extending schedule
|
||||
4. Override by block name (not by time)
|
||||
@@ -748,7 +748,7 @@ schedule BaseWorkday {
|
||||
block sleep { 22:00 - 24:00, activity: sleep }
|
||||
}
|
||||
|
||||
schedule BakerSchedule extends BaseWorkday {
|
||||
schedule BakerSchedule modifies BaseWorkday {
|
||||
override work { time: 5:00 - 13:00, location: Bakery }
|
||||
append prep { time: 3:00 - 5:00, activity: bake_prep }
|
||||
}
|
||||
@@ -788,7 +788,7 @@ schedule BakeryHours {
|
||||
### Example 5: Full Year with Events
|
||||
|
||||
```storybook
|
||||
schedule AnnualSchedule extends WorkWeek {
|
||||
schedule AnnualSchedule modifies WorkWeek {
|
||||
event Christmas on Dec 25 {
|
||||
block family { 0:00 - 24:00, activity: family_time }
|
||||
}
|
||||
@@ -813,17 +813,17 @@ schedule BaseHuman {
|
||||
block sleep { 22:00 - 24:00, activity: sleep }
|
||||
}
|
||||
|
||||
schedule Worker extends BaseHuman {
|
||||
schedule Worker modifies BaseHuman {
|
||||
block work { 9:00 - 17:00, activity: work }
|
||||
}
|
||||
|
||||
schedule Baker extends Worker {
|
||||
schedule Baker modifies Worker {
|
||||
override work { time: 5:00 - 13:00, location: Bakery }
|
||||
override breakfast { time: 13:30 - 14:00 } // Late breakfast
|
||||
append prep { time: 3:00 - 5:00, activity: bake }
|
||||
}
|
||||
|
||||
schedule RetiredBaker extends BaseHuman {
|
||||
schedule RetiredBaker modifies BaseHuman {
|
||||
// No work, just keep eating/sleeping schedule
|
||||
append hobby { time: 10:00 - 16:00, activity: hobby }
|
||||
}
|
||||
@@ -842,7 +842,7 @@ SCHEDULES Section:
|
||||
|
||||
Schedule:
|
||||
- name: String
|
||||
- parent_schedule_id: Option<u32> // extends reference
|
||||
- parent_schedule_id: Option<u32> // modifies reference
|
||||
- blocks: [ScheduleBlock...]
|
||||
- patterns: [SchedulePattern...] // weekday, season, event
|
||||
|
||||
@@ -876,7 +876,7 @@ PatternKind:
|
||||
```
|
||||
Character Martha uses BakerSchedule
|
||||
↓
|
||||
BakerSchedule extends BaseWorkday
|
||||
BakerSchedule modifies BaseWorkday
|
||||
↓
|
||||
Merge: BaseWorkday blocks + Baker overrides
|
||||
↓
|
||||
@@ -888,14 +888,14 @@ Result: Martha's schedule for today
|
||||
## 12. Implementation Plan
|
||||
|
||||
### Phase 1: AST Extension (Week 1)
|
||||
1. Add extended Schedule struct with `extends` and `items`
|
||||
1. Add extended Schedule struct with `modifies` and `items`
|
||||
2. Add ScheduleItem enum (Block, Override, Remove, etc.)
|
||||
3. Add pattern types (WeekdayPattern, SeasonPattern, EventPattern)
|
||||
4. Add DayOfWeek, Season, DateSpec enums
|
||||
5. Update existing ScheduleBlock to support optional names
|
||||
|
||||
### Phase 2: Parser Implementation (Week 1-2)
|
||||
1. Implement `extends` clause parsing
|
||||
1. Implement `modifies` clause parsing
|
||||
2. Implement override/remove/append operators
|
||||
3. Implement weekday/day patterns
|
||||
4. Implement season patterns
|
||||
@@ -970,7 +970,7 @@ Which calendar system?
|
||||
|
||||
### Must Have
|
||||
- [x] Backward compatible with current simple schedules
|
||||
- [x] Template inheritance with `extends`
|
||||
- [x] Template inheritance with `modifies`
|
||||
- [x] Override/remove/append operators
|
||||
- [x] Weekday patterns (weekday/weekend)
|
||||
- [x] Day-specific patterns (on monday)
|
||||
@@ -1000,7 +1000,7 @@ Which calendar system?
|
||||
|
||||
| Aspect | Behavior Linking | Schedule Linking |
|
||||
|--------|------------------|------------------|
|
||||
| **Inheritance** | `from Template` | `extends Base` |
|
||||
| **Inheritance** | `from Template` | `modifies Base` |
|
||||
| **Merge Rule** | Concatenation + override by name | Same |
|
||||
| **Override** | Character's behavior replaces template's | Child's block replaces parent's |
|
||||
| **Selection** | Priority + conditions | Temporal + conditions |
|
||||
|
||||
Reference in New Issue
Block a user