How to refer to previously matched items in a grammar? - grammar

I am trying to parse a BibTeX author field, and split it into its separate authors. This will help me rewriting the initials of each author. Here is a minimal example:
use v6;
my $str = '{Rockhold, Mark L and Yarwood, RR and Selker, John S}';
grammar BibTexAuthor {
token TOP {
<all-text>
}
token all-text {
'{' <authors> '}'
}
token authors {
[<author> [' and ' || <?before '}'>]]+
}
token author {
[<-[\s}]> || [' ' <!before 'and '>]]+
}
}
class BibTexAuthor-actions {
method TOP($/) {
say $/;
print "First author = ";
say $<author>.made[0];
make $/.Str;
}
method all-text($/) {
make $/.Str;
}
method authors($/) {
make $/.Str;
}
method author($/) {
make $/.Str;
}
}
my $res = BibTexAuthor.parse( $str, actions => BibTexAuthor-actions.new).made;
Output:
「{Rockhold, Mark L and Yarwood, RR and Selker, John S}」
all-text => 「{Rockhold, Mark L and Yarwood, RR and Selker, John S}」
authors => 「Rockhold, Mark L and Yarwood, RR and Selker, John S」
author => 「Rockhold, Mark L」
author => 「Yarwood, RR」
author => 「Selker, John S」
First author = Nil
Why am I not able to extract the first author in the TOP method?

Why am I not able to extract the first author in the TOP method?
Because you are not really extracting any data in the action methods. All you do is attach the string of the match to $/.made, which is not actually the data you want in the end.
If you want to have separate authors in the end, you should make an array of authors in the authors action method. For example:
use v6;
my $str = '{Rockhold, Mark L and Yarwood, RR and Selker, John S}';
grammar BibTexAuthor {
token TOP {
<all-text>
}
token all-text {
'{' <authors> '}'
}
token authors {
[<author> [' and ' || <?before '}'>]]+
}
token author {
[<-[\s}]> || [' ' <!before 'and '>]]+
}
}
class BibTexAuthor-actions {
method TOP($/) {
make { authors => $<all-text>.made };
}
method all-text($/) {
make $/<authors>.made;
}
method authors($/) {
make $/<author>».made;
}
method author($/) {
make $/.Str;
}
}
my $res = BibTexAuthor.parse( $str, actions => BibTexAuthor-actions.new).made;
say $res.perl;
prints
${:authors($["Rockhold, Mark L", "Yarwood, RR", "Selker, John S"])}
so now the .made of the top-level match is a hash, where the authors key holds an array. If you want to access the first author, you can now say
say $res<authors>[0];
to get Rockhold, Mark L

$<all-text><authors><author>[0];
Note, that I have no idea how grammars work until now. I'm learning the language as you do.
But just by looking at the datastrucure it's easy to realize it's a tree and where in that tree the value you are looking for is.
You can output any datastructure by saying
dd $someStructure;
say $someStructure.perl;
and if you find that unreadable, you can try one of the Dumper Modules

Related

Laravel Gate using User data instead of specified parameter

I'm trying to understand Gates, Permissions and Authentication better so I've created two Gates in my AuthServiceProvider. "isPermitted" checks if the user is permitted to access the given role and "isProjectRole" checks if the project given falls under the role the user is permitted to use.
My issue is that for some reason, the data passed to the isProjectRole Gate is incorrect, as it seems to be passing the User Facade instead of the project ID, which obviously comes back with the error that the User Facade can't be compared to an integer. But when I use DD to get the User, project ID and role ID it comes out as normal.
Using the DD inside of the controller
Using the DD inside the isProjectRole Gate
The routes are as follows:
Route::group(['middleware' => ['auth']], function () {
Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('welcome');
Route::get('/map/{role}/{project}', [App\Http\Controllers\MapController::class, 'index']);
});
Here's the code from my controller.
public function index(Request $request, $role, $project)
{
$user = Auth::user() ?? 'null';
// return dd('user: ' . $user->name . ' role: ' . $role . ' project: ' . $project);
if(Gate::denies('isProjectRole', $role, $project)){ // Project validation
return dd('You are not permitted to view this project.');
}
if(Gate::allows('isPermitted', $user->id, $role)){
....
Here's the code from the AuthServiceProvider.
public function boot()
{
$this->registerPolicies();
Gate::define('isPermitted', function($user, $role){
$userRole = DB::table('users')
->join('user_roles', 'users.id', '=', 'user_roles.user_id')
->join('roles', 'roles.id', '=', 'user_roles.role_id')
->select('roles.id')
->where('users.id', '=', $user->id)
->where('roles.id', '=', $role)
->first();
return $userRole ? true : false;
//return [$user, $role];
});
Gate::define('isProjectRole', function($roleId, $projectId){
// return dd($projectId, $roleId);
$projectIsInRole = DB::table('roles')
->join('role_projects', 'roles.id', '=', 'role_projects.role_id')
->join('projects', 'projects.id', '=', 'role_projects.project_id')
->select('projects.id')
->where('roles.id', '=', $roleId)
->where('projects.id', '=', $projectId)
->first();
return $projectIsInRole ? true : false;
});
}
Any help would be greatly appreciated. If I've left anything out that might help, please let me know.

Automatically update ACF field upon date

Here is what I am trying to accomplish: turn off a True/False Advanced Custom Fields(ACF) option on a post if the current date is on or past a selected date on the same post. Also after that code, turn off a Sample Lesson True/False option inside of the lessons attached to the current post.
At first, all I had was the update_sample_child_lessons function with an 'init' action (i.e.add_action( 'init', 'update_sample_child_lessons' );), but that seemed to only run when I clicked update on the post. It did work and everything switched over, but it only ran when I manually clicked Update on the post. So then I did a little research and found that a Cron job should do the trick if I want the code to run automatically without me having to click update, but for some reason I can't seem to get it to work.
So if you know of a way to accomplish what I am trying to do with the code below, or with other code that is completely different, any suggestions or help would be much appreciated.
//CRON JOB TO RUN EVERYDAY
function myprefix_custom_cron_schedule( $schedules ) {
$schedules['every_day'] = array(
'interval' => 86400, //24 HOURS IN SECONDS
'display' => __( 'Every 24 hours' ),
);
return $schedules;
}
add_filter( 'cron_schedules', 'myprefix_custom_cron_schedule' );
if ( ! wp_next_scheduled( 'myprefix_cron_hook' ) ) {
wp_schedule_event( time(), 'every_day', 'myprefix_cron_hook' );
}
//AUTOMATICALLY ADJUSTS SAMPLE LESSON FREE OPTIONS AND FREE BANNER IF DATE IS PASSED
add_action( 'myprefix_cron_hook', 'update_sample_child_lessons' );
function update_sample_child_lessons() {
$allcourses = array(
'post_type' => 'sfwd-courses', //CUSTOM POST TYPE: COURSES
'posts_per_page' => -1 //QUERY ALL OF THEM
);
$query = new WP_Query($allcourses);
if ($query->have_posts()) {
global $post;
if ( ( in_array( $post->post_type, array( 'sfwd-courses' ), true ) )) { //ONLY DO ACTION IF ON CPT OF COURSES
while ($query->have_posts()) {
$query->the_post();
$course_id = learndash_get_course_id( $post->ID ); //GET THE COURSE ID
$free = get_field('display_free_lessons', $course_id); //GET THE FREE COURSE OPTION (TRUE/FALSE TICKER)
if (!empty($free)) { //ONLY DO REST OF CODE IF FREE OPTION IS TURNED ON
$freeDate = get_field('free_until', $course_id); //GET THE DATE FIELD THAT THE COURSE IS FREE UNTIL
$currentDate = date('Ymd'); //GET CURRENT DATE
$diff = strtotime($freeDate) - strtotime($currentDate); //GET THE DIFFERENCE BETWEEN THE TWO DATES
if ($diff <= 0) { //ONLY DO REST OF CODE IF DATE DIFFERENCE IS LESS THAN OR EQUAL TO ZERO
$value = '';
update_field('display_free_lessons', $value, $course_id); //UPDATES THE FREE OPTION FIELD TO FALSE(OR NOTHING)
//LESSON CODE
$lessons = array_slice(learndash_course_get_lessons($course_id), 1); //GET ALL THE LESSONS FROM THE COURSE EXCEPT FOR THE FIRST ONE
foreach ($lessons as $lesson) {
$lessonID = $lesson->ID; //GET THE LESSON ID
$lesson_meta = get_post_meta($lessonID); //GET THE METADATA FOR THE LESSON
if ( is_array( $lesson_meta ) ) {
foreach ( $lesson_meta as $meta_key => $meta_value ) {
if ( '_sfwd-lessons' === $meta_key ) {
$lesson_settings = maybe_unserialize( $meta_value[0] ); //SOME OF THE ARRAYS ARE SERIALIZED, SO UNSERIALIZE IF NEEDED
if ( isset( $lesson_settings['sfwd-lessons_sample_lesson'] ) ) {
$lesson_settings['sfwd-lessons_sample_lesson'] = ''; //TURN OFF THE SAMPLE LESSON OPTION ON THE LESSONS
}
update_post_meta( $lessonID, $meta_key, $lesson_settings );
}
}
}
} //END FOREACH
} //END IF DIFF IS 0
wp_reset_postdata();
}
}
}
}
}
Thanks for the comment #Luke Chaffey, I was actually able to figure it out after finding I had my cron actions reversed. Below is the final code that I got working so that it runs every day at 12am:
//CRON JOB TO RUN EVERYDAY
function custom_cron_schedule( $schedules ) {
$schedules['every_day'] = array(
'interval' => 86400,
'display' => __( 'Every 24 hours' ),
);
return $schedules;
}
add_filter( 'cron_schedules', 'custom_cron_schedule' );
$ve = get_option('gmt_offset') > 0 ? '-' : '+';
if ( ! wp_next_scheduled('cron_sample_lesson' ) ) {
wp_schedule_event(strtotime('00:00 tomorrow ' . $ve .
absint(get_option('gmt_offset')) . ' HOURS'), 'daily','cron_sample_lesson' );
}
add_action('cron_sample_lesson', 'update_sample_child_lessons' );
function update_sample_child_lessons() {...

Get roles by name in a 'guildMemberAdd' handler

Since member.guild.roles.get('roleName') no longer works. I'd like to know if there's an alternative to it.
message.guild.roles.cache.find(role => role.name == 'My Role Name') is not an option, since I don't have access to message object inside the 'guildMemberAdd' handler.
My code works fine using the role id, but I'd like to make it usable for other servers.
** updating my question
This is my code:
const role1 = 'the id number here'; ---> this one I'd like to be by name
const role2 = 'the id number here');
if (collected.first().emoji.name === '😎') {
member.roles.add(role1);
} if (collected.first().emoji.name === '🟩'){
member.roles.add(role2);
} else { return}
You can access the guild from GuildMember
client.on('guildMemberAdd', member => {
// member.guild.roles.cache.find(...) - Callback similar to Array#find()
// member.guild.roles.cache.get(...) - string id parameter only
});

In typescript, how to sort exactly like MSSQL's 'order by' clause for special characters like * , + - etc

I have a Angular5 based front-end which needs to show a list of persons. A similar list is shown at the back-end application. Both lists need to be sorted exactly the same. The back-end list is fetched via a query in MSSQL DB which uses an 'order by' clause like this :
select * from PERSON where GROUP=1 order by PERSON.LAST_NAME ASC, PERSON.INITIALS ASC, PERSON.PREFIX ASC, PERSON.FIRSTNAME ASC
On the frontend, my person model looks like :
export interface Person {
id?: number;
fullName?: string;
}
The fullName here is as per this format : [LastName, Initials Prefix FirstName] eg : Adams, Mr P John.
I am using this method to sort it :
public sortByName(aPersonArr: Person[]) {
aPersonArr.sort((p1, p2) => {
let name1 = p1.fullName;
let name2 = p2.fullName;
// If both names are blank, consider them equal, If one name is blank, place it at the last
if (!name1 && !name2) {
return 0;
} else if (name1 && !name2) {
return -1;
} else if (name2 && !name1) {
return 1;
}
name1 = name1.toLowerCase();
name2 = name2.toLowerCase();
if (name1 < name2) {
return -1;
}
if (name1 > name2) {
return 1;
}
return 0;
});
}
}
But the problem occurs when a person's fullName begins with a special character because SQL Query fetches the names in this order : (Showing only the lastNames here)
*Account
,Adams
.Alkin
+Account
-Adams
Whereas my typescript code sorts them like this : (Showing only the lastNames here)
*Account
+Account
,Adams
-Adams
.Alkin
The reason that I need to have this logic at frontend is that many a times my person list is dynamically prepared and needs to be sorted right away. Is there a way to know what exact logic is used by SQL query to compare strings, so that i can use the same in my sorting method.

Yii: adding custom fields

Is there a simple way of adding custom fields to a model? Say I have a table "user" with 3 fields: id, name and surname. I want this:
$user = User::model()->findByPk(1);
$echo $user->fullName; // echoes name and surname
Please note: I want this custom field to be added via sql, smth like
$c = new CDbCriteria();
$c->select = 'CONCAT("user".name, "user".surname) as fullName';
$user = User::model()->find($c);
Problem is that fullName property is not set.
UPD:
here is the code for a little bit trickier problem -- custom field from another table. This is how it's done:
$model = Application::model();
$model->getMetaData()->columns = array_merge($model->getMetaData()->columns, array('fullName' => 'CONCAT("u".name, "u".surname)'));
$c = new CDbCriteria();
$c->select = 'CONCAT("u".name, "u".surname) as fullName';
$c->join = ' left join "user" "u" on "t".responsible_manager_id = "u".id';
$model->getDbCriteria()->mergeWith($c);
foreach ($model->findAll() as $o) {
echo '<pre>';
print_r($o->fullName);
echo '</pre>';
}
You can add a function to the User class:
public function getFullName() { return $this->name.' '.$this->surname; }
This will return the full name as if it were an attribute from the database. This is much easier than adding a calculated column to the SQL.
In model
public function getMetaData(){
$data = parent::getMetaData();
$data->columns['fullName'] = array('name' => 'fullName');
return $data;
}
Thus not recommended